Index: /branches/jh_branches/jhoblitt/DataStore/.cvsignore
===================================================================
--- /branches/jh_branches/jhoblitt/DataStore/.cvsignore	(revision 22254)
+++ /branches/jh_branches/jhoblitt/DataStore/.cvsignore	(revision 22254)
@@ -0,0 +1,6 @@
+Build
+META.yml
+Makefile
+Makefile.PL
+_build
+blib
Index: /branches/jh_branches/jhoblitt/DataStore/Build.PL
===================================================================
--- /branches/jh_branches/jhoblitt/DataStore/Build.PL	(revision 22254)
+++ /branches/jh_branches/jhoblitt/DataStore/Build.PL	(revision 22254)
@@ -0,0 +1,24 @@
+use Module::Build;
+# See perldoc Module::Build for details of how this works
+
+Module::Build->new(
+    module_name         => 'DataStore',
+    dist_version_from   => 'lib/DataStore/Parser.pm',
+    author              => 'Joshua Hoblitt <jhoblitt@cpan.org>',
+    license             => 'gpl',
+    create_makefile_pl  => 'passthrough',
+    requires            => {
+        'Carp'                  => 0,
+        'Data::Validate::URI'   => '0.01',
+        'LWP::UserAgent'        => 0,
+        'Params::Validate'      => '0.77',
+        'Regexp::Common'        => '2.113',
+    },
+    build_requires      => {
+        'Test::Warn'            => '0.08',
+    },
+    recommends          => {
+        'Test::Distribution'    => '1.22',
+    },
+#    script_files        => [qw()],
+)->create_build_script;
Index: /branches/jh_branches/jhoblitt/DataStore/Changes
===================================================================
--- /branches/jh_branches/jhoblitt/DataStore/Changes	(revision 22254)
+++ /branches/jh_branches/jhoblitt/DataStore/Changes	(revision 22254)
@@ -0,0 +1,6 @@
+Revision history for Perl module DataStore
+
+0.01 Thu Feb 23 13:55:57 2006
+    - original version; created by ExtUtils::ModuleMaker 0.44
+
+
Index: /branches/jh_branches/jhoblitt/DataStore/LICENSE
===================================================================
--- /branches/jh_branches/jhoblitt/DataStore/LICENSE	(revision 22254)
+++ /branches/jh_branches/jhoblitt/DataStore/LICENSE	(revision 22254)
@@ -0,0 +1,260 @@
+The General Public License (GPL)
+Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave,
+Cambridge, MA 02139, USA. Everyone is permitted to copy and distribute
+verbatim copies of this license document, but changing it is not allowed.
+
+Preamble
+
+The licenses for most software are designed to take away your freedom to share
+and change it. By contrast, the GNU General Public License is intended to
+guarantee your freedom to share and change free software--to make sure the
+software is free for all its users. This General Public License applies to most of
+the Free Software Foundation's software and to any other program whose
+authors commit to using it. (Some other Free Software Foundation software is
+covered by the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+When we speak of free software, we are referring to freedom, not price. Our
+General Public Licenses are designed to make sure that you have the freedom
+to distribute copies of free software (and charge for this service if you wish), that
+you receive source code or can get it if you want it, that you can change the
+software or use pieces of it in new free programs; and that you know you can do
+these things.
+
+To protect your rights, we need to make restrictions that forbid anyone to deny
+you these rights or to ask you to surrender the rights. These restrictions
+translate to certain responsibilities for you if you distribute copies of the
+software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether gratis or for a
+fee, you must give the recipients all the rights that you have. You must make
+sure that they, too, receive or can get the source code. And you must show
+them these terms so they know their rights.
+
+We protect your rights with two steps: (1) copyright the software, and (2) offer
+you this license which gives you legal permission to copy, distribute and/or
+modify the software.
+
+Also, for each author's protection and ours, we want to make certain that
+everyone understands that there is no warranty for this free software. If the
+software is modified by someone else and passed on, we want its recipients to
+know that what they have is not the original, so that any problems introduced by
+others will not reflect on the original authors' reputations.
+
+Finally, any free program is threatened constantly by software patents. We wish
+to avoid the danger that redistributors of a free program will individually obtain
+patent licenses, in effect making the program proprietary. To prevent this, we
+have made it clear that any patent must be licensed for everyone's free use or
+not licensed at all.
+
+The precise terms and conditions for copying, distribution and modification
+follow.
+
+GNU GENERAL PUBLIC LICENSE
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND
+MODIFICATION
+
+0. This License applies to any program or other work which contains a notice
+placed by the copyright holder saying it may be distributed under the terms of
+this General Public License. The "Program", below, refers to any such program
+or work, and a "work based on the Program" means either the Program or any
+derivative work under copyright law: that is to say, a work containing the
+Program or a portion of it, either verbatim or with modifications and/or translated
+into another language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not covered by
+this License; they are outside its scope. The act of running the Program is not
+restricted, and the output from the Program is covered only if its contents
+constitute a work based on the Program (independent of having been made by
+running the Program). Whether that is true depends on what the Program does.
+
+1. You may copy and distribute verbatim copies of the Program's source code as
+you receive it, in any medium, provided that you conspicuously and appropriately
+publish on each copy an appropriate copyright notice and disclaimer of warranty;
+keep intact all the notices that refer to this License and to the absence of any
+warranty; and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and you may at
+your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Program or any portion of it, thus
+forming a work based on the Program, and copy and distribute such
+modifications or work under the terms of Section 1 above, provided that you also
+meet all of these conditions:
+
+a) You must cause the modified files to carry prominent notices stating that you
+changed the files and the date of any change.
+
+b) You must cause any work that you distribute or publish, that in whole or in
+part contains or is derived from the Program or any part thereof, to be licensed
+as a whole at no charge to all third parties under the terms of this License.
+
+c) If the modified program normally reads commands interactively when run, you
+must cause it, when started running for such interactive use in the most ordinary
+way, to print or display an announcement including an appropriate copyright
+notice and a notice that there is no warranty (or else, saying that you provide a
+warranty) and that users may redistribute the program under these conditions,
+and telling the user how to view a copy of this License. (Exception: if the
+Program itself is interactive but does not normally print such an announcement,
+your work based on the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If identifiable
+sections of that work are not derived from the Program, and can be reasonably
+considered independent and separate works in themselves, then this License,
+and its terms, do not apply to those sections when you distribute them as
+separate works. But when you distribute the same sections as part of a whole
+which is a work based on the Program, the distribution of the whole must be on
+the terms of this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest your rights to
+work written entirely by you; rather, the intent is to exercise the right to control
+the distribution of derivative or collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program with the
+Program (or with a work based on the Program) on a volume of a storage or
+distribution medium does not bring the other work under the scope of this
+License.
+
+3. You may copy and distribute the Program (or a work based on it, under
+Section 2) in object code or executable form under the terms of Sections 1 and 2
+above provided that you also do one of the following:
+
+a) Accompany it with the complete corresponding machine-readable source
+code, which must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange; or,
+
+b) Accompany it with a written offer, valid for at least three years, to give any
+third party, for a charge no more than your cost of physically performing source
+distribution, a complete machine-readable copy of the corresponding source
+code, to be distributed under the terms of Sections 1 and 2 above on a medium
+customarily used for software interchange; or,
+
+c) Accompany it with the information you received as to the offer to distribute
+corresponding source code. (This alternative is allowed only for noncommercial
+distribution and only if you received the program in object code or executable
+form with such an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for making
+modifications to it. For an executable work, complete source code means all the
+source code for all modules it contains, plus any associated interface definition
+files, plus the scripts used to control compilation and installation of the
+executable. However, as a special exception, the source code distributed need
+not include anything that is normally distributed (in either source or binary form)
+with the major components (compiler, kernel, and so on) of the operating system
+on which the executable runs, unless that component itself accompanies the
+executable.
+
+If distribution of executable or object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the source
+code from the same place counts as distribution of the source code, even though
+third parties are not compelled to copy the source along with the object code.
+
+4. You may not copy, modify, sublicense, or distribute the Program except as
+expressly provided under this License. Any attempt otherwise to copy, modify,
+sublicense or distribute the Program is void, and will automatically terminate
+your rights under this License. However, parties who have received copies, or
+rights, from you under this License will not have their licenses terminated so long
+as such parties remain in full compliance.
+
+5. You are not required to accept this License, since you have not signed it.
+However, nothing else grants you permission to modify or distribute the Program
+or its derivative works. These actions are prohibited by law if you do not accept
+this License. Therefore, by modifying or distributing the Program (or any work
+based on the Program), you indicate your acceptance of this License to do so,
+and all its terms and conditions for copying, distributing or modifying the
+Program or works based on it.
+
+6. Each time you redistribute the Program (or any work based on the Program),
+the recipient automatically receives a license from the original licensor to copy,
+distribute or modify the Program subject to these terms and conditions. You
+may not impose any further restrictions on the recipients' exercise of the rights
+granted herein. You are not responsible for enforcing compliance by third parties
+to this License.
+
+7. If, as a consequence of a court judgment or allegation of patent infringement
+or for any other reason (not limited to patent issues), conditions are imposed on
+you (whether by court order, agreement or otherwise) that contradict the
+conditions of this License, they do not excuse you from the conditions of this
+License. If you cannot distribute so as to satisfy simultaneously your obligations
+under this License and any other pertinent obligations, then as a consequence
+you may not distribute the Program at all. For example, if a patent license would
+not permit royalty-free redistribution of the Program by all those who receive
+copies directly or indirectly through you, then the only way you could satisfy
+both it and this License would be to refrain entirely from distribution of the
+Program.
+
+If any portion of this section is held invalid or unenforceable under any particular
+circumstance, the balance of the section is intended to apply and the section as
+a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any patents or other
+property right claims or to contest validity of any such claims; this section has
+the sole purpose of protecting the integrity of the free software distribution
+system, which is implemented by public license practices. Many people have
+made generous contributions to the wide range of software distributed through
+that system in reliance on consistent application of that system; it is up to the
+author/donor to decide if he or she is willing to distribute software through any
+other system and a licensee cannot impose that choice.
+
+This section is intended to make thoroughly clear what is believed to be a
+consequence of the rest of this License.
+
+8. If the distribution and/or use of the Program is restricted in certain countries
+either by patents or by copyrighted interfaces, the original copyright holder who
+places the Program under this License may add an explicit geographical
+distribution limitation excluding those countries, so that distribution is permitted
+only in or among countries not thus excluded. In such case, this License
+incorporates the limitation as if written in the body of this License.
+
+9. The Free Software Foundation may publish revised and/or new versions of the
+General Public License from time to time. Such new versions will be similar in
+spirit to the present version, but may differ in detail to address new problems or
+concerns.
+
+Each version is given a distinguishing version number. If the Program specifies a
+version number of this License which applies to it and "any later version", you
+have the option of following the terms and conditions either of that version or of
+any later version published by the Free Software Foundation. If the Program does
+not specify a version number of this License, you may choose any version ever
+published by the Free Software Foundation.
+
+10. If you wish to incorporate parts of the Program into other free programs
+whose distribution conditions are different, write to the author to ask for
+permission. For software which is copyrighted by the Free Software Foundation,
+write to the Free Software Foundation; we sometimes make exceptions for this.
+Our decision will be guided by the two goals of preserving the free status of all
+derivatives of our free software and of promoting the sharing and reuse of
+software generally.
+
+NO WARRANTY
+
+11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS
+NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE
+COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM
+"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR
+IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
+ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE,
+YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
+CORRECTION.
+
+12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED
+TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY
+WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS
+PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
+ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM
+(INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY
+OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS
+BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+END OF TERMS AND CONDITIONS
Index: /branches/jh_branches/jhoblitt/DataStore/MANIFEST
===================================================================
--- /branches/jh_branches/jhoblitt/DataStore/MANIFEST	(revision 22254)
+++ /branches/jh_branches/jhoblitt/DataStore/MANIFEST	(revision 22254)
@@ -0,0 +1,10 @@
+MANIFEST
+README
+LICENSE
+Todo
+Changes
+Build.PL
+Makefile.PL
+lib/DataStore/Parser.pm
+t/00_distribution.t
+t/01_load.t
Index: /branches/jh_branches/jhoblitt/DataStore/README
===================================================================
--- /branches/jh_branches/jhoblitt/DataStore/README	(revision 22254)
+++ /branches/jh_branches/jhoblitt/DataStore/README	(revision 22254)
@@ -0,0 +1,15 @@
+pod2text DataStore.pm > README
+
+If this is still here it means the programmer was too lazy to create the readme file.
+
+You can create it now by using the command shown above from this directory.
+
+At the very least you should be able to use this set of instructions
+to install the module...
+
+perl Build.PL
+./Build
+./Build test
+./Build install
+
+If you are on a windows box you should use 'nmake' rather than 'make'.
Index: /branches/jh_branches/jhoblitt/DataStore/Todo
===================================================================
--- /branches/jh_branches/jhoblitt/DataStore/Todo	(revision 22254)
+++ /branches/jh_branches/jhoblitt/DataStore/Todo	(revision 22254)
@@ -0,0 +1,5 @@
+TODO list for Perl module DataStore
+
+- Nothing yet
+
+
Index: /branches/jh_branches/jhoblitt/DataStore/lib/DataStore/Parser.pm
===================================================================
--- /branches/jh_branches/jhoblitt/DataStore/lib/DataStore/Parser.pm	(revision 22254)
+++ /branches/jh_branches/jhoblitt/DataStore/lib/DataStore/Parser.pm	(revision 22254)
@@ -0,0 +1,111 @@
+package DataStore;
+use strict;
+
+BEGIN {
+    use Exporter ();
+    use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
+    $VERSION     = '0.01';
+    @ISA         = qw(Exporter);
+    #Give a hoot don't pollute, do not export more than needed by default
+    @EXPORT      = qw();
+    @EXPORT_OK   = qw();
+    %EXPORT_TAGS = ();
+}
+
+
+#################### subroutine header begin ####################
+
+=head2 sample_function
+
+ Usage     : How to use this function/method
+ Purpose   : What it does
+ Returns   : What it returns
+ Argument  : What it wants to know
+ Throws    : Exceptions and other anomolies
+ Comment   : This is a sample subroutine header.
+           : It is polite to include more pod and fewer comments.
+
+See Also   : 
+
+=cut
+
+#################### subroutine header end ####################
+
+
+sub new
+{
+    my ($class, %parameters) = @_;
+
+    my $self = bless ({}, ref ($class) || $class);
+
+    return $self;
+}
+
+
+#################### main pod documentation begin ###################
+## Below is the stub of documentation for your module. 
+## You better edit it!
+
+
+=head1 NAME
+
+DataStore - Module abstract (<= 44 characters) goes here
+
+=head1 SYNOPSIS
+
+  use DataStore;
+  blah blah blah
+
+
+=head1 DESCRIPTION
+
+Stub documentation for this module was created by ExtUtils::ModuleMaker.
+It looks like the author of the extension was negligent enough
+to leave the stub unedited.
+
+Blah blah blah.
+
+
+=head1 USAGE
+
+
+
+=head1 BUGS
+
+
+
+=head1 SUPPORT
+
+
+
+=head1 AUTHOR
+
+    A. U. Thor
+    CPAN ID: MODAUTHOR
+    XYZ Corp.
+    a.u.thor@a.galaxy.far.far.away
+    http://a.galaxy.far.far.away/modules
+
+=head1 COPYRIGHT
+
+This program is free software licensed under the...
+
+	The General Public License (GPL)
+	Version 2, June 1991
+
+The full text of the license can be found in the
+LICENSE file included with this module.
+
+
+=head1 SEE ALSO
+
+perl(1).
+
+=cut
+
+#################### main pod documentation end ###################
+
+
+1;
+# The preceding line will help the module return a true value
+
Index: /branches/jh_branches/jhoblitt/DataStore/t/00_distribution.t
===================================================================
--- /branches/jh_branches/jhoblitt/DataStore/t/00_distribution.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/DataStore/t/00_distribution.t	(revision 22254)
@@ -0,0 +1,25 @@
+#!/usr/bin/env perl
+
+# Copyright (C) 2005  Joshua Hoblitt
+#
+# $Id: 00_distribution.t,v 1.1.1.1 2006-02-24 00:03:06 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use lib qw( ./lib ./t );
+
+use Test::More;
+
+# example taken from Test::Distribution Pod
+
+BEGIN {
+    eval {
+        require Test::Distribution;
+    };
+    if($@) {
+        plan skip_all => 'Test::Distribution not installed';
+    } else {
+        import Test::Distribution not => qw( podcover );
+    }
+}
Index: /branches/jh_branches/jhoblitt/DataStore/t/01_load.t
===================================================================
--- /branches/jh_branches/jhoblitt/DataStore/t/01_load.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/DataStore/t/01_load.t	(revision 22254)
@@ -0,0 +1,12 @@
+# -*- perl -*-
+
+# t/001_load.t - check module loading and create testing directory
+
+use Test::More tests => 2;
+
+BEGIN { use_ok( 'DataStore::Parser' ); }
+
+my $object = DataStore::Parser->new ();
+isa_ok ($object, 'DataStore::Parser');
+
+
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/Build.PL
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/Build.PL	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/Build.PL	(revision 22254)
@@ -0,0 +1,30 @@
+use Module::Build;
+# See perldoc Module::Build for details of how this works
+
+my $build_pkg   = eval { require Apache::TestMB }
+                ? 'Apache::TestMB'
+                : 'Module::Build';
+
+$build_pkg->new(
+    module_name         => 'PS::IPP::IData',
+    dist_version_from   => 'lib/PS/IPP/IData/Server.pm',
+    author              => 'Joshua Hoblitt <jhoblitt@cpan.org>',
+    license             => 'gpl',
+    requires            => {
+        'URI'                   => 1.30,
+        'SOAP::Lite'            => 0.60,
+        'Log::Log4perl'         => 0.48,
+        'DBI'                   => 1.42,
+        'DBD::mysql'            => 2.9004,
+        'Class::Accessor::Fast' => 0,
+        'Params::Validate'      => 0,
+        'Apache::Test'          => 1.12,
+        'Test::More'            => 0.49,
+    },
+    recommends          => {
+        'Apache::DBI'           => 0.94,
+    },
+    build_requires      => {
+        'File::Temp'            => 0,
+    },
+)->create_build_script;
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/Changes
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/Changes	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/Changes	(revision 22254)
@@ -0,0 +1,5 @@
+Revision history for Perl module Poi::PixelData
+
+0.01 Fri Sep  3 10:23:31 2004
+    - first working version
+	- original version; created by ExtUtils::ModuleMaker 0.32
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/LICENSE
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/LICENSE	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/LICENSE	(revision 22254)
@@ -0,0 +1,260 @@
+The General Public License (GPL)
+Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave,
+Cambridge, MA 02139, USA. Everyone is permitted to copy and distribute
+verbatim copies of this license document, but changing it is not allowed.
+
+Preamble
+
+The licenses for most software are designed to take away your freedom to share
+and change it. By contrast, the GNU General Public License is intended to
+guarantee your freedom to share and change free software--to make sure the
+software is free for all its users. This General Public License applies to most of
+the Free Software Foundation's software and to any other program whose
+authors commit to using it. (Some other Free Software Foundation software is
+covered by the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+When we speak of free software, we are referring to freedom, not price. Our
+General Public Licenses are designed to make sure that you have the freedom
+to distribute copies of free software (and charge for this service if you wish), that
+you receive source code or can get it if you want it, that you can change the
+software or use pieces of it in new free programs; and that you know you can do
+these things.
+
+To protect your rights, we need to make restrictions that forbid anyone to deny
+you these rights or to ask you to surrender the rights. These restrictions
+translate to certain responsibilities for you if you distribute copies of the
+software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether gratis or for a
+fee, you must give the recipients all the rights that you have. You must make
+sure that they, too, receive or can get the source code. And you must show
+them these terms so they know their rights.
+
+We protect your rights with two steps: (1) copyright the software, and (2) offer
+you this license which gives you legal permission to copy, distribute and/or
+modify the software.
+
+Also, for each author's protection and ours, we want to make certain that
+everyone understands that there is no warranty for this free software. If the
+software is modified by someone else and passed on, we want its recipients to
+know that what they have is not the original, so that any problems introduced by
+others will not reflect on the original authors' reputations.
+
+Finally, any free program is threatened constantly by software patents. We wish
+to avoid the danger that redistributors of a free program will individually obtain
+patent licenses, in effect making the program proprietary. To prevent this, we
+have made it clear that any patent must be licensed for everyone's free use or
+not licensed at all.
+
+The precise terms and conditions for copying, distribution and modification
+follow.
+
+GNU GENERAL PUBLIC LICENSE
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND
+MODIFICATION
+
+0. This License applies to any program or other work which contains a notice
+placed by the copyright holder saying it may be distributed under the terms of
+this General Public License. The "Program", below, refers to any such program
+or work, and a "work based on the Program" means either the Program or any
+derivative work under copyright law: that is to say, a work containing the
+Program or a portion of it, either verbatim or with modifications and/or translated
+into another language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not covered by
+this License; they are outside its scope. The act of running the Program is not
+restricted, and the output from the Program is covered only if its contents
+constitute a work based on the Program (independent of having been made by
+running the Program). Whether that is true depends on what the Program does.
+
+1. You may copy and distribute verbatim copies of the Program's source code as
+you receive it, in any medium, provided that you conspicuously and appropriately
+publish on each copy an appropriate copyright notice and disclaimer of warranty;
+keep intact all the notices that refer to this License and to the absence of any
+warranty; and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and you may at
+your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Program or any portion of it, thus
+forming a work based on the Program, and copy and distribute such
+modifications or work under the terms of Section 1 above, provided that you also
+meet all of these conditions:
+
+a) You must cause the modified files to carry prominent notices stating that you
+changed the files and the date of any change.
+
+b) You must cause any work that you distribute or publish, that in whole or in
+part contains or is derived from the Program or any part thereof, to be licensed
+as a whole at no charge to all third parties under the terms of this License.
+
+c) If the modified program normally reads commands interactively when run, you
+must cause it, when started running for such interactive use in the most ordinary
+way, to print or display an announcement including an appropriate copyright
+notice and a notice that there is no warranty (or else, saying that you provide a
+warranty) and that users may redistribute the program under these conditions,
+and telling the user how to view a copy of this License. (Exception: if the
+Program itself is interactive but does not normally print such an announcement,
+your work based on the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If identifiable
+sections of that work are not derived from the Program, and can be reasonably
+considered independent and separate works in themselves, then this License,
+and its terms, do not apply to those sections when you distribute them as
+separate works. But when you distribute the same sections as part of a whole
+which is a work based on the Program, the distribution of the whole must be on
+the terms of this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest your rights to
+work written entirely by you; rather, the intent is to exercise the right to control
+the distribution of derivative or collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program with the
+Program (or with a work based on the Program) on a volume of a storage or
+distribution medium does not bring the other work under the scope of this
+License.
+
+3. You may copy and distribute the Program (or a work based on it, under
+Section 2) in object code or executable form under the terms of Sections 1 and 2
+above provided that you also do one of the following:
+
+a) Accompany it with the complete corresponding machine-readable source
+code, which must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange; or,
+
+b) Accompany it with a written offer, valid for at least three years, to give any
+third party, for a charge no more than your cost of physically performing source
+distribution, a complete machine-readable copy of the corresponding source
+code, to be distributed under the terms of Sections 1 and 2 above on a medium
+customarily used for software interchange; or,
+
+c) Accompany it with the information you received as to the offer to distribute
+corresponding source code. (This alternative is allowed only for noncommercial
+distribution and only if you received the program in object code or executable
+form with such an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for making
+modifications to it. For an executable work, complete source code means all the
+source code for all modules it contains, plus any associated interface definition
+files, plus the scripts used to control compilation and installation of the
+executable. However, as a special exception, the source code distributed need
+not include anything that is normally distributed (in either source or binary form)
+with the major components (compiler, kernel, and so on) of the operating system
+on which the executable runs, unless that component itself accompanies the
+executable.
+
+If distribution of executable or object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the source
+code from the same place counts as distribution of the source code, even though
+third parties are not compelled to copy the source along with the object code.
+
+4. You may not copy, modify, sublicense, or distribute the Program except as
+expressly provided under this License. Any attempt otherwise to copy, modify,
+sublicense or distribute the Program is void, and will automatically terminate
+your rights under this License. However, parties who have received copies, or
+rights, from you under this License will not have their licenses terminated so long
+as such parties remain in full compliance.
+
+5. You are not required to accept this License, since you have not signed it.
+However, nothing else grants you permission to modify or distribute the Program
+or its derivative works. These actions are prohibited by law if you do not accept
+this License. Therefore, by modifying or distributing the Program (or any work
+based on the Program), you indicate your acceptance of this License to do so,
+and all its terms and conditions for copying, distributing or modifying the
+Program or works based on it.
+
+6. Each time you redistribute the Program (or any work based on the Program),
+the recipient automatically receives a license from the original licensor to copy,
+distribute or modify the Program subject to these terms and conditions. You
+may not impose any further restrictions on the recipients' exercise of the rights
+granted herein. You are not responsible for enforcing compliance by third parties
+to this License.
+
+7. If, as a consequence of a court judgment or allegation of patent infringement
+or for any other reason (not limited to patent issues), conditions are imposed on
+you (whether by court order, agreement or otherwise) that contradict the
+conditions of this License, they do not excuse you from the conditions of this
+License. If you cannot distribute so as to satisfy simultaneously your obligations
+under this License and any other pertinent obligations, then as a consequence
+you may not distribute the Program at all. For example, if a patent license would
+not permit royalty-free redistribution of the Program by all those who receive
+copies directly or indirectly through you, then the only way you could satisfy
+both it and this License would be to refrain entirely from distribution of the
+Program.
+
+If any portion of this section is held invalid or unenforceable under any particular
+circumstance, the balance of the section is intended to apply and the section as
+a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any patents or other
+property right claims or to contest validity of any such claims; this section has
+the sole purpose of protecting the integrity of the free software distribution
+system, which is implemented by public license practices. Many people have
+made generous contributions to the wide range of software distributed through
+that system in reliance on consistent application of that system; it is up to the
+author/donor to decide if he or she is willing to distribute software through any
+other system and a licensee cannot impose that choice.
+
+This section is intended to make thoroughly clear what is believed to be a
+consequence of the rest of this License.
+
+8. If the distribution and/or use of the Program is restricted in certain countries
+either by patents or by copyrighted interfaces, the original copyright holder who
+places the Program under this License may add an explicit geographical
+distribution limitation excluding those countries, so that distribution is permitted
+only in or among countries not thus excluded. In such case, this License
+incorporates the limitation as if written in the body of this License.
+
+9. The Free Software Foundation may publish revised and/or new versions of the
+General Public License from time to time. Such new versions will be similar in
+spirit to the present version, but may differ in detail to address new problems or
+concerns.
+
+Each version is given a distinguishing version number. If the Program specifies a
+version number of this License which applies to it and "any later version", you
+have the option of following the terms and conditions either of that version or of
+any later version published by the Free Software Foundation. If the Program does
+not specify a version number of this License, you may choose any version ever
+published by the Free Software Foundation.
+
+10. If you wish to incorporate parts of the Program into other free programs
+whose distribution conditions are different, write to the author to ask for
+permission. For software which is copyrighted by the Free Software Foundation,
+write to the Free Software Foundation; we sometimes make exceptions for this.
+Our decision will be guided by the two goals of preserving the free status of all
+derivatives of our free software and of promoting the sharing and reuse of
+software generally.
+
+NO WARRANTY
+
+11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS
+NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE
+COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM
+"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR
+IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
+ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE,
+YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
+CORRECTION.
+
+12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED
+TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY
+WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS
+PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
+ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM
+(INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY
+OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS
+BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+END OF TERMS AND CONDITIONS
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/MANIFEST
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/MANIFEST	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/MANIFEST	(revision 22254)
@@ -0,0 +1,53 @@
+Build.PL
+Changes
+LICENSE
+MANIFEST
+META.yml
+Makefile.PL
+README
+TODO
+Todo
+bootstrap.sh
+docs/apache_setup.txt
+docs/c_api.h
+docs/calc_gid.c
+docs/database_setup.txt
+docs/design.txt
+docs/requirements.txt
+docs/setup.txt
+docs/tmp.txt
+iData.wsdl
+lib/PS/IPP/IData/Client.pm
+lib/PS/IPP/IData/Client/HTTP.pm
+lib/PS/IPP/IData/Client/Log.pm
+lib/PS/IPP/IData/Server.pm
+lib/PS/IPP/IData/Server/Apache.pm
+lib/PS/IPP/IData/Server/Log.pm
+lib/PS/IPP/IData/Server/SQL.pm
+lib/PS/IPP/IData/Util.pm
+scripts/bench_test.pl
+scripts/imageserver.pl
+scripts/ptest.pl
+scripts/stest.pl
+t/01_load.t
+t/02_server_setup.t
+t/03_server_create_object.t
+t/04_server_replicate_object.t
+t/05_server_lock_object.t
+t/06_server_unlock_object.t
+t/07_server_find_instances.t
+t/08_server_delete_instance.t
+t/09_server_stat_object.t
+t/10_client_new.t
+t/11_client_create.t
+t/12_client_replicate.t
+t/13_client_cull.t
+t/14_client_lock.t
+t/15_client_unlock.t
+t/16_client_find_instances.t
+t/TEST.PL
+t/Test/IData.pm
+t/apache_03_server_create_object.x
+t/apache_04_server_replicate_object.x
+t/apache_05_server_lock_object.x
+t/conf/extra.conf.in
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/Makefile.PL
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/Makefile.PL	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/Makefile.PL	(revision 22254)
@@ -0,0 +1,27 @@
+unless (eval "use Module::Build::Compat 0.02; 1" ) {
+  print "This module requires Module::Build to install itself.\n";
+
+  require ExtUtils::MakeMaker;
+  my $yn = ExtUtils::MakeMaker::prompt
+    ('  Install Module::Build from CPAN?', 'y');
+
+  if ($yn =~ /^y/i) {
+    require Cwd;
+    require File::Spec;
+    require CPAN;
+
+    # Save this 'cause CPAN will chdir all over the place.
+    my $cwd = Cwd::cwd();
+    my $makefile = File::Spec->rel2abs($0);
+
+    CPAN::Shell->install('Module::Build::Compat');
+
+    chdir $cwd or die "Cannot chdir() back to $cwd: $!";
+    exec $^X, $makefile, @ARGV;  # Redo now that we have Module::Build
+  } else {
+    warn " *** Cannot install without Module::Build.  Exiting ...\n";
+    exit 1;
+  }
+}
+Module::Build::Compat->run_build_pl(args => \@ARGV);
+Module::Build::Compat->write_makefile();
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/README
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/README	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/README	(revision 22254)
@@ -0,0 +1,16 @@
+pod2text Poi::PixelData.pm > README
+
+If this is still here it means the programmer was too lazy to create the readme file.
+
+You can create it now by using the command shown above from this directory.
+
+At the very least you should be able to use this set of instructions
+to install the module...
+
+perl Build.PL
+./Build
+./Build test
+./Build install
+
+
+If you are on a windows box you should use 'nmake' rather than 'make'.
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/TODO
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/TODO	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/TODO	(revision 22254)
@@ -0,0 +1,17 @@
+cache some database tables (updated at some interval) 
+reuse/cache database statement handles
+code tiding/consistency
+volume handling for instance placement
+file creation retrying
+server config file
+make Apache::DBI behave
+WSDL
+split SOAP out of Client.pm
+error string support
+
+distant future
+--
+volume size/% in-use discovery
+server side per client lock tracking
+client side lookup table of filehandles/locks
+Perl only?, scoped lock release/filehandle destruction
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/Todo
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/Todo	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/Todo	(revision 22254)
@@ -0,0 +1,5 @@
+TODO list for Perl module Poi::PixelData
+
+- Nothing yet
+
+
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/bootstrap-moa-mp1.sh
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/bootstrap-moa-mp1.sh	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/bootstrap-moa-mp1.sh	(revision 22254)
@@ -0,0 +1,1 @@
+perl Build.PL -http_conf /usr/local/apache/conf/httpd.conf -apxs /usr/local/apache/bin/apxs -httpd /usr/local/apache/bin/httpd 
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/docs/apache_setup.txt
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/docs/apache_setup.txt	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/docs/apache_setup.txt	(revision 22254)
@@ -0,0 +1,37 @@
+- add the "idata" group to /etc/group
+- configure Apache to run as the idata group. 
+
+#
+# CGI interface
+#
+
+ScriptAlias /idata /usr/local/apache/perl/imageserver.pl
+
+<Directory "/usr/local/apache/perl">
+        AllowOverride None
+        Options None
+        Order allow,deny
+        Allow from all
+</Directory>
+
+PerlModule Apache::PerlRun
+<Location /perl>
+        SetHandler perl-script
+        PerlHandler Apache::PerlRun
+        Options ExecCGI
+        allow from all
+        PerlSendHeader On
+</Location>
+
+#
+# PS::IPP::IData::Apache interface
+#
+
+PerlModule Apache::DBI
+PerlModule PS::IPP::IData::Apache
+
+<Location /idata>
+        SetHandler perl-script
+        PerlHandler PS::IPP::IData::Apache
+</Location>
+
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/docs/c_api.h
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/docs/c_api.h	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/docs/c_api.h	(revision 22254)
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2004 Joshua Hoblitt
+ *
+ * $Id: c_api.h,v 1.1.1.1 2004-12-22 02:16:23 jhoblitt Exp $
+ */
+
+// should there be an argument for "suggested" storage location?
+int add( const char *ext_id, const char *data, int size )
+
+int delete( const char *ext_id )
+
+// should there be an argument for "suggested" storage location?
+int update( const char *ext_id, const char *data, int size )
+
+// should there be an argument for "suggested" storage location?
+int move( const char *old_ext_id, const char *new_ext_id )
+
+// open returns a fd 
+int open( const char *ext_id )
+
+int close( const char *ext_id )
+
+// maybe open and close are a bad idea, perhaps...
+int read( const char *ext_id, char *data )
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/docs/calc_gid.c
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/docs/calc_gid.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/docs/calc_gid.c	(revision 22254)
@@ -0,0 +1,5 @@
+#include <stdio.h>
+
+main () {
+    printf( "%d\n", 'i'+'m'+'a'+'g'+'e'+'d'+'a'+'t'+'a' + 's'+'e'+'r'+'v'+'e'+'r' );
+}
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/docs/database_setup.txt
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/docs/database_setup.txt	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/docs/database_setup.txt	(revision 22254)
@@ -0,0 +1,147 @@
+# Copyright (C) 2004  Joshua Hoblitt
+#
+# $Id: database_setup.txt,v 1.1.1.1 2004-12-22 02:16:23 jhoblitt Exp $
+
+### Database Setup
+
+DROP DATABASE IF EXISTS pixel;
+CREATE DATABASE pixel;
+INSERT INTO db VALUES ('%','pixel','pixel','y','y','y','y','y','y','y','y','y','y','y','y');
+INSERT INTO user
+(Host,User,Password,Select_priv,Insert_priv,Update_priv,Delete_priv,Create_priv,Drop_priv,Index_priv,Alter_priv,Show_db_priv)
+VALUES ('%','pixel',password('pixel'),'y','y','y','y','y','y','y','y','y');
+INSERT INTO user
+(Host,User,Password,Select_priv,Insert_priv,Update_priv,Delete_priv,Create_priv,Drop_priv,Index_priv,Alter_priv,Show_db_priv)
+VALUES ('localhost','pixel',password('pixel'),'y','y','y','y','y','y','y','y','y');
+FLUSH PRIVILEGES;
+
+USE pixel;
+
+### MySQL 4.0
+
+DROP TABLE IF EXISTS storage_object;
+CREATE TABLE storage_object (
+    so_id BIGINT NOT NULL AUTO_INCREMENT,
+    ext_id VARCHAR(255) BINARY NOT NULL UNIQUE,
+    class_id TINYINT NOT NULL,
+    comment VARCHAR(255),
+    read_lock TINYINT DEFAULT 0 NOT NULL,
+    write_lock ENUM( 'write' ),
+    epoch TIMESTAMP NOT NULL,
+    PRIMARY KEY(so_id),
+    KEY(ext_id(64)),
+    KEY(class_id)
+) ENGINE=innodb;
+
+DROP TABLE IF EXISTS instance;
+CREATE TABLE instance ( 
+    ins_id BIGINT NOT NULL AUTO_INCREMENT,
+    so_id BIGINT NOT NULL,
+    uri VARCHAR(255) BINARY NOT NULL UNIQUE,
+    sha1sum CHAR(40),
+    assigned_location BOOL,
+    epoch TIMESTAMP NOT NULL,
+    atime TIMESTAMP NOT NULL,
+    PRIMARY KEY(ins_id),
+    KEY(so_id),
+    KEY(uri(64))
+) ENGINE=innodb;
+
+DROP TABLE IF EXISTS lock_record;
+CREATE TABLE lock_record (
+        so_id BIGINT NOT NULL,
+        type ENUM( 'read', 'write' ) NOT NULL,
+        epoch TIMESTAMP,
+        KEY(so_ID)
+    ) ENGINE=innodb;
+
+DROP TABLE IF EXISTS volume;
+CREATE TABLE volume (
+        vol_id INT NOT NULL AUTO_INCREMENT,
+        name VARCHAR(255) UNIQUE BINARY NOT NULL,
+        uri VARCHAR(255) BINARY NOT NULL,
+        key(vol_id)
+    ) ENGINE=innodb;
+
+DROP TABLE IF EXISTS class;
+CREATE TABLE class (
+        class_id TINYINT NOT NULL AUTO_INCREMENT,
+        instance TINYINT NOT NULL,
+        priority TINYINT,
+        comment VARCHAR(255),
+        key(class_id)
+    ) ENGINE=innodb;
+
+### MySQL 4.1
+
+DROP TABLE IF EXISTS storage_object;
+CREATE TABLE storage_object (
+        so_id BIGINT NOT NULL AUTO_INCREMENT,
+        ext_id VARCHAR(255) UNIQUE BINARY NOT NULL,
+        comment VARCHAR(255),
+        epoch TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+        PRIMARY KEY(so_id),
+        KEY(ext_id(20))
+    ) ENGINE=innodb;
+
+
+DROP TABLE IF EXISTS instance;
+CREATE TABLE instance ( 
+        ins_id BIGINT NOT NULL AUTO_INCREMENT,
+        so_id BIGINT NOT NULL,
+        uri VARCHAR(255) BINARY NOT NULL,
+        sha1sum CHAR(40) ASCII,
+        read_lock TINYINT NOT NULL,
+        write_lock ENUM( 'write', 'wait' ),
+        atime TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+        PRIMARY KEY(ins_id),
+        KEY(so_id)
+    ) ENGINE=innodb;
+
+DROP TABLE IF EXISTS lock_record;
+CREATE TABLE lock_record (
+        ins_id BIGINT NOT NULL,
+        type ENUM( 'read', 'write', 'wait' ) NOT NULL,
+        epoch TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+    ) ENGINE=innodb;
+
+        epoch TIMESTAMP,
+
+### Initialization
+
+# NFS
+
+INSERT INTO volume VALUES (1,'po01','file:/tmp/po01');
+INSERT INTO volume VALUES (2,'po02','file:/tmp/po02');
+INSERT INTO volume VALUES (3,'po03','file:/tmp/po03');
+INSERT INTO volume VALUES (4,'po04','file:/tmp/po04');
+INSERT INTO volume VALUES (5,'po05','file:/tmp/po05');
+INSERT INTO volume VALUES (6,'po06','file:/tmp/po06');
+INSERT INTO volume VALUES (7,'po07','file:/tmp/po07');
+INSERT INTO volume VALUES (8,'po08','file:/tmp/po08');
+
+INSERT INTO class VALUES (1,3,0,'raw image data');
+
+### Example queries
+
+LOAD DATA INFILE '/home/moa/jhoblitt/tmp/storage_objects.txt' INTO TABLE storage_object; 
+LOAD DATA INFILE '/home/moa/jhoblitt/tmp/instances.txt' INTO TABLE instance; 
+
+SELECT * FROM storage_object LEFT JOIN instance ON storage_object.so_id=instance.so_id WHERE storage_object.ext_id = "foobarbaz1.fits";
+SELECT * FROM storage_object LEFT JOIN instance USING (so_id) WHERE storage_object.ext_id = "foobarbaz1.fits";
+
+SET AUTOCOMMIT = 0;
+BEGIN;
+UPDATE instance SET read_lock=read_lock+1 WHERE ins_id=1;
+INSERT INTO lock_record values (1, 'read', NULL);
+COMMIT;
+
+SET AUTOCOMMIT = 0;
+BEGIN;
+INSERT INTO storage_object values (NULL, 'myfooedbar.fits', 'mr. wizard made this', NULL);
+INSERT INTO instance values (NULL, LAST_INSERT_ID(), 'file://tmp/bat/man/12345a', 'da39a3ee5e6b4b0d3255bfef95601890afd80709',0, NULL, NULL);
+COMMIT;
+
+IF select write_lock from instance WHERE id=1 THEN
+ELSE
+ENDIF
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/docs/design.txt
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/docs/design.txt	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/docs/design.txt	(revision 22254)
@@ -0,0 +1,163 @@
+# Copryight (C) 2004  Joshua Hoblitt
+#
+# $Id: design.txt,v 1.1.1.1 2004-12-22 02:16:23 jhoblitt Exp $
+
+Terminology
+--
+    - Storage Object
+There is one Storage Object per resource (a file) that is being managed by the
+IPP Pixel Data Server.  A Storage Object maps an External Identifier (eg.
+file name) to a Storage ID that is internal to the IPP Pixel Data Server.  In
+addition, creation time and comments may be stored in a Storage Object.
+Storage Objects /do not/ contain the storage location of a resource as we need
+to allow for N copies of a resource to be stored.  The actual storage location
+of a resource is held in a Storage Object Instance, which maps to a Storage ID
+in a many-to-one relationship.
+
+    - [Storage Object] Instance
+A Storage Object Instance, or just Instance, represents a single copy of a
+resource being managed by the IPP Pixel Data Server. An Instance contains an
+Instance ID, the location of the resource as a URI, a checksum of the
+resource, a Storage ID, a read lock semaphore (unsigned integer), a write lock
+mutex (enum( 'write', 'wait', 'free' ) - is this better then using an int and
+constants?), and a UTC timestamp of the last modification date & time.
+
+    - Lock Record
+A Lock Record is used to double check the indicated lock state of an Instance
+and can be used to identify system failures.  It must contain the Instance ID
+of of the locked instance and the type of lock, eg, enum( 'read', 'write').
+
+Locking semantics
+--
+A lock is required to either read from or write to a Storage Object Instance
+being managed by the IPP Pixel Data Server.  Stated another way, locking
+granularity is managed on the level of Storage Object Instances instead of on
+the more abstract Storage Objects.  Although, from the client perspective,
+operations are carried out on a Storage Object.   The locking state of a
+Storage Object Instance is maintained by in the Storage Object Instance and a
+Lock Record.
+
+Two types of locks are used on an Instance, a semaphore is used to track read
+locks and an exclusive mutex is for write locking.
+
+    - Read lock
+In order to read from a Storage Object Instance the Read Lock semaphore must
+first be incremented by one and a Lock Record created.  After the read is
+completed the Lock Record must be deleted and the Read Lock semaphore
+decremented by one.  The incrementation of the Read Lock and Lock Record
+creation or the decrementation of the Read Lock and the Lock Record deletion
+must happen atomically.  A Read lock can not be aquired unless the Write mutex
+is in the state of 'free'.  If a Read lock can not be aquired, a client must
+not "spin lock" on the Instanace ID as it may be in the process of being
+removed.  Instead, the "spin lock" should be re-resolving the external
+identifier to a Storage ID, and the Storage ID to Storage Instances.
+
+    - Write lock
+In order to obtain a Write lock on a Storage Object Instance the Write Lock
+mutex must be in the state of 'free' and the Read lock semaphore must have a
+value of zero.  A client should either "spin lock" or fail if all the Lock
+mutex is set or the Read Semaphore has a non-zero value.  A Lock Record must
+be created atomically with the setting of the Write mutex.  The setting of the
+Write mutex and Lock Record insertion or the clearing of the Write mutex and
+the Lock Record deletion must happen atomically.  If the Write lock has a
+state of 'free' and the Read lock has a non-zero value, the Write lock may be
+set to the 'wait' state to prevent new Read locks from being aquired.
+
+Storage Object Operations
+--
+    - Add
+The resource must already be on disk (having been named through some sort of
+mechanism to generate new filenames).  A new Storage Object must be created with a new/unqiue  Storage ID and has
+an associated with an External Identifier.  At least one Instance must be
+created atomicaly at the same time.  Each new Instance must be initially created with a Write lock.
+The resource may then be written to it's storage location.  When the writing
+of the resource is completed the Write lock must be cleared as early as is
+feasible.  The association of an External Identifier must wait until Instance
+creation is completed
+
+    - Delete
+In order to remove a Storage Object, a Write lock must be obtained on
+/all/ associated Instances.  Then the resources are removed.  As each resource
+is deleted the corresponding Instance must be deleted.  After all Instances are
+removed the Storage Object must then be deleted.
+
+    - Update
+Storage Object Instances are not truly written to.  Instead, a new set of
+Storage Object Instances and a new Storage Object are created in parallel.
+
+Write locks must be aquired on all the orignal Intances of a Storage Object.
+Then an Add operation is performed to create a new Storage Object and
+associated Instances.  Then the External Identifier must be atomically moved
+from the original Storage Object to the new Storage Object.  After the
+External Identifier migration, the original Storage Object and it's Instances
+should be removed with a special case of the Delete operation (one that will
+operate on already held locks).  If the old Storage Object is merely
+abandoned, ie. it still exists but doesn't have an External Identifier, it and
+the associated Instances will be removed during the next Consistency Sweep.
+If a Delete is not peformed, the Write locks on the orignal Instances must be
+released. [use a dead Storage Object table or flag, similar to mark and sweep
+garbage collection?]
+
+    - Move
+This is the same operation as an Update except the new Instances are identical
+to the old.
+
+    - Open
+Sets a Read lock on an Instances of a Storage Object and determines it's
+storage location.  A Read lock must only be placed on one Instance of a
+Storage Object.  All Instances of a Storage Object should be iterated through
+in a "spin lock" until a Read lock is aquired.
+
+    - Close
+Releases a Read lock aquired with open.
+
+The lock must be released as soon as is feasible.
+
+Lock sweeping
+--
+In the event that a Storage Object operation fails to complete successfully
+stale locks will have to be identified and removed from the IPP Pixel Data
+Server Database.  This should be done periodically by comparing the entries
+in the Lock table to the list of active nodes maintained by the IPP
+Controller.  It should also happen as soon as possible after a node goes
+offline (triggered by the IPP Controller marking a node as offline?).  A sweep
+must be /completed/ before an offline node can be marked on-line.
+
+Once a node is determined to be offline all entries in the Lock table set by
+that node should be identified.   The locks on the Storage Object
+Instances pointed to by those entries should then be rolled back and Lock
+Record entries themselves must be removed from the lock table.
+
+Consistency sweeping
+--
+Periodically the IPP Pixel Data Server meta-data and Storage Object will need
+to be checked for sanity.  This would be similar to running fsck on a modern
+filesystem.  Consistency sweeping should include Lock sweeping and should be
+considered a superset.
+
+All Storage Objects must have at least once associated Instance.
+
+All Storage Object Instances must be associated with a Storage Object.
+
+All Storage Object Instances with a lock set must have the correct number of
+corresponding Lock Records.
+
+All Lock Records must be plausible, ie. the Instance pointed at must be in a
+corresponding state.
+
+Storage ID Allocation
+--
+Storage IDs are a signed 64-bit integer.  Negative values are considered
+reserved for future use.  Allocation should be incremental and, as we are
+unlikely to exceed 2^63 Storage Objects in the life time of the project, never
+reused.
+
+Allocated via [SQL auto-increment/stored procedure/app server?]
+
+Instance ID Allocation
+--
+Indentical to a Storage ID but exists in a unique namespace.
+
+Storage Location determination
+--
+???
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/docs/requirements.txt
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/docs/requirements.txt	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/docs/requirements.txt	(revision 22254)
@@ -0,0 +1,23 @@
+# Copyright (C) 2004  Joshua Hoblitt
+#
+# $Id: requirements.txt,v 1.1.1.1 2004-12-22 02:16:23 jhoblitt Exp $
+
+- store all image data aquired during the first two years of PS1's
+  operations.  Approximately 10^8 files (OTA level).
+
+- injest new data from the summit at the same rate it is produced when
+  operating at a cadence of 40s between integrations.  Approximately 400Mb/s.
+
+- support phase 1 & 2 processing. x files per t_min
+
+- support phase 3 & 4 processing. x files per t_min
+
+- support accessing small blocks of a file (ie., seek)
+
+what are the science drivers for these 3?
+--
+- data corruption is guarenteed to be detectable.
+
+- relibably store xx.x% of files 
+
+- xx.x% of stored images must be retreivable in xs.
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/docs/setup.txt
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/docs/setup.txt	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/docs/setup.txt	(revision 22254)
@@ -0,0 +1,33 @@
+
+- building and install the Perl modules (see: README)
+
+- create and initialize the database (see: docs/database_setup.txt)
+
+- setup the server
+
+As a CGI, work with either Apache 1.3.x or 2.0.x
+
+cp scripts/imagesever.pl /var/www/localhost/cgi-bin
+chmod 755 /var/www/localhost/cgi-bin/imageserver.pl
+touch /tmp/imageserver.log
+chmod 1777 /tmp/imageserver.log
+
+- test the imageserver.pl is working
+
+telnet localhost 80
+Trying 127.0.0.1...
+Connected to localhost.
+Escape character is '^]'.
+GET /cgi-bin/imageserver.pl HTTP/1.0
+
+HTTP/1.1 411 Length Required
+Date: Tue, 28 Sep 2004 21:15:58 GMT
+Server: Apache/2.0.49 (Gentoo/Linux)
+Content-Length: 0
+Connection: close
+Content-Type: text/plain; charset=ISO-8859-1
+
+Connection closed by foreign host.
+
+- if you get an HTTP 5xx code check your apache error_log
+
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/docs/tmp.txt
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/docs/tmp.txt	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/docs/tmp.txt	(revision 22254)
@@ -0,0 +1,44 @@
+client
+--
+new
+
+new_file
+                new_file
+                open_file
+
+find_file
+    find_instance
+
+open_file
+    find_file
+
+delete_file
+                delete_file
+    nuke
+
+copy_file
+    open_file
+    new_file
+
+move_file
+    copy_file
+    delete_file
+
+replicate_file
+    open_file
+                replicate_file
+
+cull_file
+    find_instance
+    delete_instance
+
+delete_instance
+    nuke
+                delete_instance
+
+find_instance
+                find_instance
+
+# missing
+file_stat
+lock ?
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/lib/Nebulous/Server.pm
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/lib/Nebulous/Server.pm	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/lib/Nebulous/Server.pm	(revision 22254)
@@ -0,0 +1,660 @@
+# Copyright (c) 2004  Joshua Hoblitt
+#
+# $Id: Server.pm,v 1.1.1.1 2004-12-22 02:16:24 jhoblitt Exp $
+
+package PS::IPP::IData::Server;
+
+use strict;
+use warnings;
+no warnings qw( uninitialized );
+
+our $VERSION = '0.01';
+
+use DBI;
+use PS::IPP::IData::Util qw( :standard );
+use PS::IPP::IData::Server::Log;
+use PS::IPP::IData::Server::SQL;
+use Log::Log4perl;
+use Params::Validate qw( validate_pos SCALAR );
+
+my %class = (
+    raw => 1,
+);
+
+my $log;
+my $sql;
+my $db;
+
+sub setup {
+    my $self = shift;
+
+    my ( $dsn, $user, $passwd ) = validate_pos( @_,
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR,
+        },
+    );
+
+    $log = Log::Log4perl::get_logger( "PS::IPP::IData::Server" );
+    $sql = PS::IPP::IData::Server::SQL->new;
+
+    $log->debug( "entered - @_" );
+
+    eval {
+        $db = DBI->connect(
+            $dsn,
+            $user,
+            $passwd,
+            {
+                RaiseError => 1,
+                PrintError => 0,
+                AutoCommit => 0,
+            },
+        );
+
+        $db->do( $sql->set_transaction_model );
+
+        $db->commit;
+    };
+    if ( $@ ) {
+        $db->rollback if $db;
+        $log->logdie( "database error: $@" );
+    }
+
+    $log->debug( "connected to database: ", sub { $db->data_sources; } );
+
+    $log->debug( "leaving" );
+
+    return 1;
+}
+
+sub create_object {
+    my $self = shift;
+
+    $log->debug( "entered - @_" );
+
+    my ( $key, $class, $volume, $comment ) = validate_pos( @_,
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is + integer' => sub { $_[0] =~ /^\d+$/ },
+            },
+            default     => 0,
+        },
+        {
+            type        => SCALAR,
+            optional    => 1,
+        },
+        {
+            type        => SCALAR,
+            optional    => 1,
+        },
+    );
+
+    # TODO do some intelligent volume allocated 
+    # add hooks to make this decision based on avaiable space
+    $volume = _get_storage_volume( $volume );
+
+    # check that the comment isn't too long
+    # check that the class requested is valid
+    # check that the volume requested is valid
+
+    my $uri;
+    my $ins_id;
+
+    eval {
+        {
+            # create storage_object
+            my $query = $db->prepare( $sql->new_object ); 
+            $query->execute( $key, $class, $comment );
+        }
+
+        {
+            # create instance with no URI
+            my $query = $db->prepare( $sql->new_object_instance );
+            $query->execute;
+        }
+
+        {
+            # get instance ID
+            my $query = $db->prepare( $sql->last_insert_id );
+            $query->execute;
+            ( $ins_id ) = $query->fetchrow_array;
+        }
+    };
+    if ( $@ ) {
+        $db->rollback;
+        $log->logdie( "database error: $@" );
+    }
+
+    $uri = "$volume/$key.$ins_id";
+
+    $log->debug( "generated uri $uri");
+
+    my $path = _get_file_path( $uri );
+
+    # TODO add some stuff here to retry if unsucessful
+    eval {
+        _create_empty_file( $path );
+    };
+    if ( $@ ) {
+        $db->rollback;
+        $log->logdie( $@ );
+    }
+
+    eval {
+        {
+            my $query = $db->prepare( $sql->update_instance_uri );
+            $query->execute( $uri, $ins_id );
+        }
+
+        $db->commit;
+    };
+    $log->logdie( "database error: $@" ) if $@;
+
+    $log->debug( "leaving" );
+
+    return $uri;
+}
+
+sub replicate_object {
+    my $self = shift;
+
+    $log->debug( "entered - @_" );
+
+    my ( $key, $volume ) = validate_pos( @_,
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR,
+            optional    => 1,
+        },
+    );
+
+    # TODO
+    $volume = _get_storage_volume();
+
+    my $path;
+    my $uri;
+
+    my $so_id;
+    my $ins_id;
+    eval {
+        {
+            my $query = $db->prepare( $sql->get_object_instances );
+            my $rows = $query->execute( $key );
+
+            unless ( $rows > 0 ) {
+                $db->rollback;
+                $log->logdie( "storage object does not exist" );
+            }
+
+            $so_id = $query->fetchrow_hashref->{ 'so_id' };
+        }
+
+        {
+            my $query = $db->prepare( $sql->new_instance );
+            $query->execute( $so_id );
+        }
+
+        {
+            my $query = $db->prepare( $sql->last_insert_id );
+            $query->execute();
+
+            ( $ins_id ) = $query->fetchrow_array;
+        }
+
+        {
+            $uri = "$volume/$key.$ins_id";
+            $log->debug( "generated uri $uri");
+
+            my $query = $db->prepare( $sql->update_instance_uri );
+            $query->execute( $uri, $ins_id );
+        }
+    };
+    if ( $@ ) {
+        $db->rollback;
+        $log->logdie( "database error: $@" );
+    }
+
+
+    eval {
+        $path = _get_file_path( $uri );
+        $path = _create_empty_file( $path );
+    };
+    if ( $@ ) {
+        $db->rollback;
+        $log->logdie( $@ );
+    }
+
+    $db->commit;
+
+    $log->debug( "leaving" );
+
+    return $uri;
+}
+
+sub lock_object {
+    my $self = shift;
+
+    $log->debug( "entered - @_" );
+
+    my ( $key, $type ) = validate_pos( @_,
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is read or write' => sub { $_[0] =~ /^(?:read|write)$/ },
+            },
+        },
+    );
+
+    my $so_id;
+    my $read_lock;
+    my $write_lock;
+
+    eval {
+        # this will set update locks
+        my $query = $db->prepare( $sql->get_object_locks );
+        my $rows = $query->execute( $key );
+        unless ( $rows == 1 ) {
+            $log->logdie( "storage object does not exist" );
+        }
+
+        my $row = $query->fetchrow_hashref;
+
+        $so_id      = $row->{ 'so_id' };
+        $read_lock  = $row->{ 'read_lock' };
+        $write_lock = $row->{ 'write_lock' };
+    };
+    if ( $@ ) {
+        $db->rollback;
+        $log->logdie( "database error: $@" );
+    }
+
+    if ( $type eq 'write' ) {
+        # can't set a write lock twice and
+        # can't set a write lock if there are read locks
+        if ( $write_lock ) {
+            eval { $db->rollback; };
+            $log->logdie( "database error: $@" ) if $@;
+
+            $log->logdie( "can not write lock twice" );
+        }
+            
+        if ( $read_lock > 0 ) {
+            eval { $db->rollback; };
+            $log->logdie( "database error: $@" ) if $@;
+
+            $log->logdie( "can not write lock after read lock" );
+        }
+
+        eval {
+            my $query = $db->prepare( $sql->set_write_lock );
+            my $rows = $query->execute( $key );
+        
+            # if we affected more then one row something very bad has happened.
+            unless ( $rows == 1 ) {
+                $log->logdie( "affected row count is $rows instead of 1" );
+            }
+
+            $db->commit;
+        };
+        if ( $@ ) {
+            $db->rollback;
+            $log->logdie( "database error: $@" );
+        }
+
+        $log->debug( "leaving" );
+
+        return 1;
+    }
+
+    if ( $type eq 'read' ) {
+        # can't set a read lock if there's a write lock
+        if ( $write_lock ) {
+            eval { $db->rollback; };
+            $log->logdie( "database error: $@" ) if $@;
+
+            $log->logdie( "can not read lock after write lock" );
+        }
+
+        eval {
+            my $query = $db->prepare( $sql->increment_read_lock );
+            my $rows = $query->execute( $key );
+        
+            # if we affected more then one row something very bad has happened.
+            unless ( $rows == 1 ) {
+                $log->logdie( "affected row count is $rows instead of 1" );
+            }
+
+            $db->commit;
+        };
+        if ( $@ ) {
+            $db->rollback;
+            $log->logdie( "database error: $@" );
+        }
+
+        $log->debug( "leaving" );
+
+        return 1;
+    }
+
+    # never reached
+    $log->logdie( "this should never happen" );
+}
+
+sub unlock_object {
+    my $self = shift;
+
+    $log->debug( "entered - @_" );
+
+    my ( $key, $type ) = validate_pos( @_,
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is read or write' => sub { $_[0] =~ /^(?:read|write)$/ },
+            },
+        },
+    );
+
+    my $so_id;
+    my $read_lock;
+    my $write_lock;
+
+    eval {
+        # this will set update locks
+        my $query = $db->prepare( $sql->get_object_locks );
+        my $rows = $query->execute( $key );
+        unless ( $rows == 1 ) {
+            $log->logdie( "storage object does not exist" );
+        }
+
+        my $row = $query->fetchrow_hashref;
+
+        $so_id      = $row->{ 'so_id' };
+        $read_lock  = $row->{ 'read_lock' };
+        $write_lock = $row->{ 'write_lock' };
+    };
+    if ( $@ ) {
+        $db->rollback;
+        $log->logdie( "database error: $@" );
+    }
+
+    if ( $type eq 'write' ) {
+        # can't remove a write lock if it doesn't exist
+        if ( $read_lock ) {
+            eval { $db->rollback; };
+            $log->logdie( "database error: $@" ) if $@;
+           
+            $log->logdie( "can not have a write lock under a read lock" );
+        }
+        unless ( $write_lock ) {
+            eval { $db->rollback; };
+            $log->logdie( "database error: $@" ) if $@;
+
+            $log->logdie( "can not remove non-existant write lock" );
+        }
+
+        eval {
+            my $query = $db->prepare( $sql->delete_write_lock );
+            my $rows = $query->execute( $key );
+        
+            # if we affected more then one row something very bad has happened.
+            unless ( $rows == 1 ) {
+                $log->logdie( "affected row count is $rows instead of 1" );
+            }
+
+            $db->commit;
+        };
+        if ( $@ ) {
+            $db->rollback;
+            $log->logdie( "database error: $@" );
+        }
+
+        $log->debug( "leaving" );
+
+        return 1;
+    }
+
+    if ( $type eq 'read' ) {
+        # can't remove a read lock if there's a write lock and
+        # can't remove a read lock if there aren't any
+        if ( $write_lock ) {
+            eval { $db->rollback; };
+            $log->logdie( "database error: $@" ) if $@;
+           
+            $log->logdie( "can not have a read lock under a write lock" );
+        }
+           
+        if ( $read_lock == 0 ) {
+            eval { $db->rollback; };
+            $log->logdie( "database error: $@" ) if $@;
+           
+            $log->logdie( "can not remove non-existant read lock" );
+        }
+
+        eval {
+            my $query = $db->prepare( $sql->decrement_read_lock );
+            my $rows = $query->execute( $key );
+        
+            # if we affected more then one row something very bad has happened.
+            unless ( $rows == 1 ) {
+
+                $log->logdie( "affected row count is $rows instead of 1" );
+            }
+
+            $db->commit;
+        };
+        if ( $@ ) {
+            $db->rollback;
+            $log->logdie( "database error: $@" );
+        }
+
+        $log->debug( "leaving" );
+
+        return 1;
+    }
+
+    # never reached
+    $log->logdie( "this should never happen" );
+}
+
+
+sub find_instances {
+    my $self = shift;
+
+    $log->debug( "entered - @_" );
+
+    my ( $key, $volume ) = validate_pos( @_,
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR,
+            optional    => 1,
+        },
+    );
+
+    # TODO restrict search to a volume
+
+    my $query;
+
+    eval {
+        $query = $db->prepare( $sql->get_object_instances );
+        $query->execute( $key );
+    };
+    $log->logdie( "database error: $@" ) if $@;
+
+    my @locations;
+
+    while ( my $row = $query->fetchrow_hashref ) {
+        my $instance = $row->{ 'uri' };
+        push @locations, $instance if $instance;
+    }
+
+    $log->logdie( "no instances found" ) unless ( scalar @locations );
+
+    $log->debug( "leaving" );
+
+    return \@locations;
+}
+
+sub delete_instance {
+    my $self = shift;
+
+    $log->debug( "entered - @_" );
+
+    my ( $uri ) = validate_pos( @_,
+        {
+            type => SCALAR,
+        },
+    );
+
+    eval {
+        my $so_id;
+        my $instances;
+        # get so_id
+        {
+            my $query = $db->prepare( $sql->get_object_from_uri );
+            my $rows = $query->execute( $uri );
+
+            unless ( $rows > 0 ) {
+                $log->logdie( "no instance is associated with uri" );
+            }
+
+            $so_id = $query->fetchrow_hashref->{ 'so_id' };
+        }
+
+        {
+            my $query = $db->prepare( $sql->get_instance_count );
+            $query->execute( $so_id );
+
+            $instances = $query->fetchrow_hashref->{ 'count(ins_id)' };
+        }
+
+        # remove instance
+        {
+            my $query = $db->prepare( $sql->delete_instance );
+            my $rows = $query->execute( $uri );
+            
+            # if we affected something other then one row something very bad has happened
+            unless ( $rows == 1 ) {
+                $log->logdie( "affected row count is $rows instead of 1" );
+            }
+        }
+
+        # if we just deleted the last instance associated with a storage object
+        # remove it too
+        if ( $instances == 1 ) {
+            # we just removed the last instance
+            my $query = $db->prepare( $sql->delete_object );
+            my $rows = $query->execute( $so_id );
+
+            # if we affected something other then one row something very bad has happened
+            unless ( $rows == 1 ) {
+                $log->logdie( "affected row count is $rows instead of 1" );
+            }
+        }
+
+        $db->commit;
+    };
+    if ( $@ ) {
+        $db->rollback;
+        $log->logdie( "database error: $@" );
+    }
+
+    $log->debug( "leaving" );
+
+    return 1;
+}
+
+sub stat_object {
+    my $self = shift;
+
+    $log->debug( "entered - @_" );
+
+    my ( $key ) = validate_pos( @_,
+        {
+            type => SCALAR,
+        },
+    );
+
+    my $stat;
+    eval {
+        my $query = $db->prepare( $sql->get_object );
+        my $rows = $query->execute( $key );
+
+        unless ( $rows == 1 ) {
+            $log->logdie( "no storage object found" );
+        }
+
+        $stat = $query->fetchrow_arrayref;
+
+        $db->commit;
+    };
+    if ( $@ ) {
+        $db->rollback;
+        $log->logdie( "database error: $@" );
+    }
+
+    $log->debug( "leaving" );
+
+    return $stat;
+}
+
+sub _get_storage_volume {
+    $log->debug( "entered - @_" );
+
+    my $name = shift;
+
+    my $volume;
+    {
+        my $query;
+        eval {
+            # TODO cache this?
+            my $rows;
+            if ( $name ) {
+                $query = $db->prepare( qq{ SELECT uri FROM volume WHERE name = ? ORDER BY rand() LIMIT 1 } );
+                $rows = $query->execute( $name );
+            } else {
+                $query = $db->prepare( qq{ SELECT uri FROM volume ORDER BY rand() LIMIT 1 } );
+                $rows = $query->execute;
+            }
+
+            # there has to be atleast one storage volume
+            unless ( $rows > 0 ) {
+                $db->rollback;
+
+                $log->logdie( "affected row count is $rows instead of 1" );
+            } else {
+                $db->commit;
+            }
+        };
+        $log->logdie( "database error: $@" ) if $@;
+
+        $volume = $query->fetchrow_hashref->{ 'uri' };
+    }
+
+    $log->debug( "leaving" );
+
+    return $volume;
+}
+
+1;
+
+__END__
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/lib/Nebulous/Server/Apache.pm
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/lib/Nebulous/Server/Apache.pm	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/lib/Nebulous/Server/Apache.pm	(revision 22254)
@@ -0,0 +1,21 @@
+# Copyright (C) 2004  Joshua Hoblitt
+#
+# $Id
+
+package PS::IPP::IData::Server::Apache;
+
+use strict;
+
+our $VERSION = 0.01;
+
+use SOAP::Transport::HTTP;
+
+my $server = SOAP::Transport::HTTP::Apache
+    ->dispatch_to( 'PS::IPP::IData::Server' )
+    ->options({ compress_threshold => 10 * 1024 });
+
+sub handler { $server->handler(@_); }
+
+1;
+
+__END__
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/lib/Nebulous/Server/Log.pm
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/lib/Nebulous/Server/Log.pm	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/lib/Nebulous/Server/Log.pm	(revision 22254)
@@ -0,0 +1,27 @@
+# Copyright (c) 2004  Joshua Hoblitt
+#
+# $Id: Log.pm,v 1.1.1.1 2004-12-22 02:16:24 jhoblitt Exp $
+
+package PS::IPP::IData::Server::Log;
+
+use strict;
+
+our $VERSION = '0.01';
+
+use Log::Log4perl;
+                                                                                                                                          
+my $conf = q(
+    log4perl.category.PS.IPP.IData.Server = DEBUG, SERVERLOGFILE
+
+    log4perl.appender.SERVERLOGFILE           = Log::Log4perl::Appender::File
+    log4perl.appender.SERVERLOGFILE.filename  = /tmp/idata_server.log
+    log4perl.appender.SERVERLOGFILE.mode      = append
+    log4perl.appender.SERVERLOGFILE.layout    = Log::Log4perl::Layout::PatternLayout
+    log4perl.appender.SERVERLOGFILE.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} | %H | %p | %M - %m%n
+);
+                                                                                                                          
+Log::Log4perl::init( \$conf );
+
+1;
+
+__END__
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/lib/Nebulous/Server/SQL.pm
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/lib/Nebulous/Server/SQL.pm	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/lib/Nebulous/Server/SQL.pm	(revision 22254)
@@ -0,0 +1,107 @@
+# Copyright (c) 2004  Joshua Hoblitt
+#
+# $Id: SQL.pm,v 1.1.1.1 2004-12-22 02:16:24 jhoblitt Exp $
+
+package PS::IPP::IData::Server::SQL;
+
+use strict;
+
+our $VERSION = '0.01';
+
+use base qw( Class::Accessor::Fast );
+
+my %sql = (
+    set_transaction_model   => qq{
+        SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED
+    },
+    update_instance_uri => qq{
+        UPDATE instance SET uri = ? WHERE ins_id = ?
+    },
+    last_insert_id      => qq{
+        SELECT LAST_INSERT_ID()
+    },
+    new_object          => qq{
+        INSERT INTO storage_object
+        VALUES (NULL, ?, ?, ?, 0, NULL, NULL)
+    },
+    delete_object       => qq{
+        DELETE FROM storage_object
+        WHERE so_id = ?
+    },
+    new_object_instance  => qq{
+        INSERT INTO instance 
+        VALUES (NULL, LAST_INSERT_ID(), 'error', NULL, NULL, NULL, NULL)
+    },
+    new_instance        => qq{
+        INSERT INTO instance
+        VALUES (NULL, ?, 'error', NULL,NULL, NULL, NULL)
+    },
+    get_object          => qq{
+        SELECT *
+        FROM storage_object
+        WHERE ext_id = ?
+    },
+    get_object_locks    => qq{
+        SELECT so_id, read_lock, write_lock 
+        FROM storage_object
+        WHERE ext_id = ?
+        FOR UPDATE
+    },
+    set_write_lock      => qq{
+        UPDATE storage_object
+        SET write_lock = 'write'
+        WHERE ext_id = ?
+    },
+    delete_write_lock   => qq{
+        UPDATE storage_object
+        SET write_lock = NULL
+        WHERE ext_id = ?
+    },
+    increment_read_lock => qq{
+        UPDATE storage_object
+        SET read_lock = read_lock+1
+        WHERE ext_id = ?
+    },
+    decrement_read_lock => qq{
+        UPDATE storage_object
+        SET read_lock = read_lock-1
+        WHERE ext_id = ?
+    },
+    delete_instance     => qq{
+        DELETE FROM instance
+        WHERE uri = ?
+    },
+    get_object_from_uri   => qq{
+        SELECT so_id
+        FROM instance
+        WHERE uri = ?
+    },
+    get_instance_count   => qq{
+        SELECT count(ins_id)
+        FROM instance
+        WHERE so_id = ?
+    },
+    get_object_instances    => qq{
+        SELECT storage_object.so_id, uri, assigned_location
+        FROM storage_object
+        LEFT JOIN instance
+        USING (so_id)
+        WHERE storage_object.ext_id = ?
+    },
+);
+
+__PACKAGE__->mk_ro_accessors( keys %sql );
+
+sub new {
+    my $class = shift;
+
+    my $self = \%sql;
+
+    bless $self, $class || ref $class;
+
+    return $self;
+}
+
+1;
+
+__END__
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/nebulous.wsdl
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/nebulous.wsdl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/nebulous.wsdl	(revision 22254)
@@ -0,0 +1,96 @@
+<?xml version="1.0"?>
+
+<definitions name="idata" targetNamespace="http://pan-starrs.ifa.hawaii.edu/PS/IPP/IData/Server">
+
+    <types>
+        <schema targetNamespace="http://pan-starrs.ifa.hawaii.edu/PS/IPP/IData/Server"
+            xmlns="http://www.w3.org/1999/XMLSchema">
+
+            <element name="create_object">
+                <complexType>
+                    <sequence>
+                        <element name="key"     type="string"/>
+                        <element name="class"   type="int"/>
+                        <element name="volume"  type="string"/>
+                        <element name="comment" type="string"/>
+                    </sequence>
+                </complexType>
+            </element>
+
+            <element name="create_objectResponse">
+                <complexType>
+                    <all>
+                        <element name="uri" type="string"/>
+                    </all>
+                </complexType>
+            </element>
+
+            <element name="create_objectFault">
+                <complexType>
+                    <all>
+                        <element name="errorMessage" type="string"/>
+                    </all>
+                </complexType>
+            </element>
+
+        </schema>
+    </types>
+
+    <message name="create_objectRequest">
+        <part name="body" element="esxsd:create_object"/>
+    </message>
+
+    <message name="create_objectResponse">
+        <part name="body" element="esxsd:create_objectResponse"/>
+    </message>
+
+    <portType name="create_objectPortType">
+        <operation name="create_object">
+            <input message ="es:create_objectRequest"/>
+            <output message="es:create_objectResponse"/>
+            <fault message ="es:create_objectFault"/>
+        </operation>
+    </portType>
+
+    <binding name="idataSoapBinding"
+        type="es:GetidatarPortType">
+
+        <soap:binding style="document"
+            transport="http://schemas.xmlsoap.org/soap/http"/>
+
+        <operation name="Getidata">
+            <soap:operation
+                soapAction="http://www.snowboard-info.com/EndorsementSearch"/>
+            <input>
+                <soap:body use="literal"
+                    namespace="http://schemas.snowboard-info.com/EndorsementSearch.xsd"/>
+            </input>
+
+            <output>
+                <soap:body use="literal"
+                    namespace="http://schemas.snowboard-info.com/EndorsementSearch.xsd"/>
+            </output>
+
+            <fault>
+                <soap:body use="literal"
+                    namespace="http://schemas.snowboard-info.com/EndorsementSearch.xsd"/>
+            </fault>
+        </operation>
+    </binding>
+
+
+  <service name="EndorsementSearchService">
+
+    <documentation>snowboarding-info.com Endorsement Service</documentation> 
+
+    <port name="GetEndorsingBoarderPort"
+          binding="es:EndorsementSearchSoapBinding">
+
+      <soap:address
+location="http://www.snowboard-info.com/EndorsementSearch"/>
+
+    </port>
+
+  </service>
+
+</definitions>
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/scripts/bench_test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/scripts/bench_test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/scripts/bench_test.pl	(revision 22254)
@@ -0,0 +1,28 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use lib "./lib";
+use Benchmark qw( cmpthese );
+
+use PS::IPP::IData::Client;
+
+my $idata = PS::IPP::IData::Client->new(
+    proxy   => 'http://localhost:80/idata'
+);
+
+my $key = "foobarbazbongbungbutufu";
+cmpthese( 10, {
+        'Image Server' => sub {
+            {
+                my $fh = $idata->create( $key );
+                close $fh;
+                $idata->unlock( $key, 'write' );
+            }
+
+            $idata->delete( $key );
+        },
+    },
+    'all',
+);
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/scripts/imageserver.pl
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/scripts/imageserver.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/scripts/imageserver.pl	(revision 22254)
@@ -0,0 +1,14 @@
+#!/usr/bin/perl -w
+
+# Copyright (C) 2004  Joshua Hoblitt
+#
+# $Id
+ 
+use strict;
+
+use SOAP::Transport::HTTP;
+use PS::IPP::IData::Server;
+   
+SOAP::Transport::HTTP::CGI
+    -> dispatch_to( 'PS::IPP::IData::Server' )
+    -> handle;
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/scripts/ptest.pl
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/scripts/ptest.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/scripts/ptest.pl	(revision 22254)
@@ -0,0 +1,51 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use lib "./lib";
+
+use PS::IPP::IData::Client;
+
+my $idata = PS::IPP::IData::Client->new(
+    uri     => 'http://localhost:80/PS/IPP/IData/Server',
+    proxy   => 'http://localhost:80/idata'
+);
+
+my $key = shift;
+
+my $kids = shift || 1;
+
+foreach my $id ( 1..$kids ) {
+    my $pid = fork;
+
+    unless ( $pid )  {
+        my $fname = "${key}_$id";
+        my $fh = $idata->create( $fname );
+        die "can't create file $fname" unless $fh;
+
+        print $fh "fooby\n";
+
+        close $fh;
+        $idata->unlock( $fname, 'write' );
+
+        $fh = $idata->open( $fname, 'read' ) or die "can't open file";
+        close $fh;
+        $idata->unlock( $fname, 'read' );
+
+        $idata->replicate( $fname );
+        $idata->cull( $fname );
+        $idata->find( $fname );
+        $idata->copy( $fname, $fname . "_copy" );
+        $idata->move( $fname, $fname . "_move" );
+        $idata->delete( $fname . "_copy" );
+        $idata->delete( $fname . "_move" );
+
+        exit 0;
+    }
+}
+
+while ( $kids ) {
+    wait();
+    $kids --;
+}
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/scripts/stest.pl
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/scripts/stest.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/scripts/stest.pl	(revision 22254)
@@ -0,0 +1,33 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use lib "./lib";
+
+use PS::IPP::IData::Client; # trace => 'debug';
+
+my $idata = PS::IPP::IData::Client->new(
+    proxy   => 'http://localhost:80/idata'
+);
+
+my $fname = shift;
+
+#my $fh = $idata->create( $fname );
+#$idata->unlock( $fname, 'write' );
+
+$idata->create( $fname, 1 );
+$idata->unlock( $fname, 'write' );
+$idata->delete( $fname );
+
+__END__
+
+{
+    my $fh = $pd->create( $key );
+    die "can't create file $fname" unless $fh;
+    print $fh "fooby\n";
+    close $fh;
+    #$idata->unlock( $fname, 'write' );
+    my $filesize =  (stat( $key ))[7];
+    print "created file $key, ${filesize}bytes\n";
+}
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/t/01_load.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/t/01_load.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/t/01_load.t	(revision 22254)
@@ -0,0 +1,19 @@
+# -*- perl -*-
+
+# t/001_load.t - check module loading and create testing directory
+
+use strict;
+use warnings;
+
+use lib qw( ./t ./lib );
+
+use Test::More tests => 8;
+
+BEGIN { use_ok( 'PS::IPP::IData::Client' ); }
+BEGIN { use_ok( 'PS::IPP::IData::Client::Log' ); }
+BEGIN { use_ok( 'PS::IPP::IData::Client::HTTP' ); }
+BEGIN { use_ok( 'PS::IPP::IData::Util' ); }
+BEGIN { use_ok( 'PS::IPP::IData::Server' ); }
+BEGIN { use_ok( 'PS::IPP::IData::Server::Apache' ); }
+BEGIN { use_ok( 'PS::IPP::IData::Server::SQL' ); }
+BEGIN { use_ok( 'PS::IPP::IData::Server::Log' ); }
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/t/02_server_setup.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/t/02_server_setup.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/t/02_server_setup.t	(revision 22254)
@@ -0,0 +1,35 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 2;
+
+use lib qw( ./t ./lib );
+
+use PS::IPP::IData::Server;
+use Test::IData;
+
+Test::IData->setup;
+
+ok(
+    PS::IPP::IData::Server->setup(
+        "DBI:mysql:database=test:host=localhost",
+        "test",
+        "",
+    ),
+    "connect",
+);
+
+Test::IData->setup;
+
+eval {
+    PS::IPP::IData::Server->setup(
+        "DBI:mysql:database=foobar:host=localhost",
+        "baz",
+        "boo",
+    );
+};
+like( $@, qr/DBI connect.*? failed/, "bad dsn/user/pass" );
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/t/03_server_create_object.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/t/03_server_create_object.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/t/03_server_create_object.t	(revision 22254)
@@ -0,0 +1,84 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 11;
+
+use lib qw( ./t ./lib );
+
+use PS::IPP::IData::Server;
+use PS::IPP::IData::Util qw( :standard );
+use Test::IData;
+
+my $idata = "PS::IPP::IData::Server";
+
+$idata->setup(
+    "DBI:mysql:database=test:host=localhost",
+    "test",
+    "",
+);
+
+Test::IData->setup;
+
+{
+    # key
+    my $uri = $idata->create_object( "foo" );
+
+    ok( -e _get_file_path( $uri ), "file exists" );
+    like( $uri, qr/^file:\//, "valid URI" );
+}
+
+Test::IData->setup;
+
+{
+    # key, class
+    my $uri = $idata->create_object( "foo", 0 );
+
+    ok( -e _get_file_path( $uri ), "file exists" );
+    like( $uri, qr/^file:\//, "valid URI" );
+}
+
+Test::IData->setup;
+
+{
+    # key, class, volume
+    my $uri = $idata->create_object( "foo", 0, "node01" );
+
+    ok( -e _get_file_path( $uri ), "file exists" );
+    like( $uri, qr/^file:\//, "valid URI" );
+}
+
+Test::IData->setup;
+
+{
+    # key, class, volume, comment
+    my $uri = $idata->create_object( "foo", 0, "node01", "this is foo" );
+
+    ok( -e _get_file_path( $uri ), "file exists" );
+    like( $uri, qr/^file:\//, "valid URI" );
+}
+
+Test::IData->setup;
+
+eval {
+    $idata->create_object( "foo" );
+    $idata->create_object( "foo" );
+};
+like( $@, qr/Duplicate entry/, "object already exists" );
+
+Test::IData->setup;
+
+eval {
+    $idata->create_object();
+};
+like( $@, qr/2 - 4 were expected/, "no params" );
+
+Test::IData->setup;
+
+eval {
+    $idata->create_object( 1, 2, 3, 4, 5 );
+};
+like( $@, qr/2 - 4 were expected/, "too many params" );
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/t/04_server_replicate_object.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/t/04_server_replicate_object.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/t/04_server_replicate_object.t	(revision 22254)
@@ -0,0 +1,79 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 11;
+
+use lib qw( ./t ./lib );
+
+use PS::IPP::IData::Server;
+use PS::IPP::IData::Util qw( :standard );
+use Test::IData;
+
+my $idata = "PS::IPP::IData::Server";
+
+$idata->setup(
+    "DBI:mysql:database=test:host=localhost",
+    "test",
+    "",
+);
+
+Test::IData->setup;
+
+{
+    # key
+    $idata->create_object( "foo" );
+    my $uri = $idata->replicate_object( "foo" );
+
+    ok( -e _get_file_path( $uri ), "file exists" );
+    like( $uri, qr/^file:\//, "valid URI" );
+}
+
+Test::IData->setup;
+
+{
+    # key, $volume
+    $idata->create_object( "foo" );
+    my $uri = $idata->replicate_object( "foo", "node01" );
+
+    ok( -e _get_file_path( $uri ), "file exists" );
+    like( $uri, qr/^file:\//, "valid URI" );
+}
+
+Test::IData->setup;
+
+{
+    # key, $volume
+    $idata->create_object( "foo" );
+    my $uri1 = $idata->replicate_object( "foo", "node01" );
+    my $uri2 = $idata->replicate_object( "foo", "node02" );
+
+    ok( -e _get_file_path( $uri1 ), "file exists" );
+    ok( -e _get_file_path( $uri2 ), "file exists" );
+    like( $uri1, qr/^file:\//, "valid URI" );
+    like( $uri2, qr/^file:\//, "valid URI" );
+}
+
+Test::IData->setup;
+
+eval {
+    $idata->replicate_object( "foo" );
+};
+like( $@, qr/storage object does not exist/, "storage object does not exist" );
+
+Test::IData->setup;
+
+eval {
+    $idata->replicate_object();
+};
+like( $@, qr/1 - 2 were expected/, "no params" );
+
+Test::IData->setup;
+
+eval {
+    $idata->replicate_object( 1, 2, 3 );
+};
+like( $@, qr/1 - 2 were expected/, "too many params" );
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/t/05_server_lock_object.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/t/05_server_lock_object.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/t/05_server_lock_object.t	(revision 22254)
@@ -0,0 +1,119 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 13;
+
+use lib qw( ./t ./lib );
+
+use PS::IPP::IData::Server;
+use PS::IPP::IData::Util qw( :standard );
+use Test::IData;
+
+my $idata = "PS::IPP::IData::Server";
+
+$idata->setup(
+    "DBI:mysql:database=test:host=localhost",
+    "test",
+    "",
+);
+
+Test::IData->setup;
+
+{
+    $idata->create_object( "foo" );
+
+    ok( $idata->lock_object( "foo", "read" ), "read lock" );
+}
+
+Test::IData->setup;
+
+{
+    $idata->create_object( "foo" );
+
+    ok( $idata->lock_object( "foo", "read" ), "read lock" );
+    ok( $idata->lock_object( "foo", "read" ), "read lock" );
+}
+
+Test::IData->setup;
+
+{
+    $idata->create_object( "foo" );
+
+    ok( $idata->lock_object( "foo", "write" ), "write lock" );
+}
+
+Test::IData->setup;
+
+eval {
+    $idata->lock_object( "foo", "read" );
+};
+like( $@, qr/storage object does not exist/, "storage object does not exist" );
+
+Test::IData->setup;
+
+eval {
+    $idata->lock_object( "foo", "write" );
+};
+like( $@, qr/storage object does not exist/, "storage object does not exist" );
+
+Test::IData->setup;
+
+eval {
+    $idata->create_object( "foo" );
+
+    $idata->lock_object( "foo", "write" );
+    $idata->lock_object( "foo", "write" );
+};
+like( $@, qr/can not write lock twice/, "can not write lock twice" );
+
+Test::IData->setup;
+
+eval {
+    $idata->create_object( "foo" );
+
+    $idata->lock_object( "foo", "read" );
+    $idata->lock_object( "foo", "write" );
+};
+like( $@, qr/can not write lock after read lock/, "can not write lock after read lock" );
+
+Test::IData->setup;
+
+eval {
+    $idata->create_object( "foo" );
+
+    $idata->lock_object( "foo", "write" );
+    $idata->lock_object( "foo", "read" );
+};
+like( $@, qr/can not read lock after write lock/, "can not read lock after write lock" );
+
+Test::IData->setup;
+
+eval {
+    $idata->lock_object();
+};
+like( $@, qr/2 were expected/, "no params" );
+
+Test::IData->setup;
+
+eval {
+    $idata->lock_object( "foo" );
+};
+like( $@, qr/2 were expected/, "not enough params" );
+
+Test::IData->setup;
+
+eval {
+    $idata->lock_object( "foo", "both" );
+};
+like( $@, qr/is read or write/, "not read or write" );
+
+Test::IData->setup;
+
+eval {
+    $idata->lock_object( "foo", 'read', 3 );
+};
+like( $@, qr/2 were expected/, "too many params" );
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/t/06_server_unlock_object.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/t/06_server_unlock_object.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/t/06_server_unlock_object.t	(revision 22254)
@@ -0,0 +1,134 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 14;
+
+use lib qw( ./t ./lib );
+
+use PS::IPP::IData::Server;
+use PS::IPP::IData::Util qw( :standard );
+use Test::IData;
+
+my $idata = "PS::IPP::IData::Server";
+
+$idata->setup(
+    "DBI:mysql:database=test:host=localhost",
+    "test",
+    "",
+);
+
+Test::IData->setup;
+
+{
+    $idata->create_object( "foo" );
+
+    $idata->lock_object( "foo", "read" );
+
+    ok( $idata->unlock_object( "foo", "read" ), "read unlock" );
+}
+
+Test::IData->setup;
+
+{
+    $idata->create_object( "foo" );
+
+    $idata->lock_object( "foo", "read" );
+    $idata->lock_object( "foo", "read" );
+
+    ok( $idata->unlock_object( "foo", "read" ), "read unlock" );
+    ok( $idata->unlock_object( "foo", "read" ), "read unlock" );
+}
+
+Test::IData->setup;
+
+{
+    $idata->create_object( "foo" );
+
+    $idata->lock_object( "foo", "write" );
+
+    ok( $idata->unlock_object( "foo", "write" ), "write unlock" );
+}
+
+Test::IData->setup;
+
+eval {
+    $idata->unlock_object( "foo", "read" );
+};
+like( $@, qr/storage object does not exist/, "storage object does not exist" );
+
+Test::IData->setup;
+
+eval {
+    $idata->unlock_object( "foo", "write" );
+};
+like( $@, qr/storage object does not exist/, "storage object does not exist" );
+
+Test::IData->setup;
+
+eval {
+    $idata->create_object( "foo" );
+
+    $idata->unlock_object( "foo", "read" );
+};
+like( $@, qr/can not remove non-existant read lock/, "no lock set" );
+
+Test::IData->setup;
+
+eval {
+    $idata->create_object( "foo" );
+
+    $idata->unlock_object( "foo", "write" );
+};
+like( $@, qr/can not remove non-existant write lock/, "no lock set" );
+
+Test::IData->setup;
+
+eval {
+    $idata->create_object( "foo" );
+    $idata->lock_object( "foo", "write" );
+
+    $idata->unlock_object( "foo", "read" );
+};
+like( $@, qr/can not have a read lock under a write lock/, "read unlock under write lock" );
+
+Test::IData->setup;
+
+eval {
+    $idata->create_object( "foo" );
+    $idata->lock_object( "foo", "read" );
+
+    $idata->unlock_object( "foo", "write" );
+};
+like( $@, qr/can not have a write lock under a read lock/, "write unlock under read lock" );
+
+Test::IData->setup;
+
+eval {
+    $idata->unlock_object();
+};
+like( $@, qr/2 were expected/, "no params" );
+
+Test::IData->setup;
+
+eval {
+    $idata->unlock_object( "foo" );
+};
+like( $@, qr/2 were expected/, "not enough params" );
+
+Test::IData->setup;
+
+eval {
+    $idata->unlock_object( "foo", "both" );
+};
+like( $@, qr/is read or write/, "not read or write" );
+
+Test::IData->setup;
+
+eval {
+    $idata->unlock_object( "foo", 'read', 3 );
+};
+like( $@, qr/2 were expected/, "too many params" );
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/t/07_server_find_instances.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/t/07_server_find_instances.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/t/07_server_find_instances.t	(revision 22254)
@@ -0,0 +1,90 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 7;
+
+use lib qw( ./t ./lib );
+
+use PS::IPP::IData::Server;
+use PS::IPP::IData::Util qw( :standard );
+use Test::IData;
+
+my $idata = "PS::IPP::IData::Server";
+
+$idata->setup(
+    "DBI:mysql:database=test:host=localhost",
+    "test",
+    "",
+);
+
+Test::IData->setup;
+
+{
+    # key
+    my $uri = $idata->create_object( "foo" );
+
+    my $locations = $idata->find_instances( "foo" );
+
+    is( $uri, $locations->[0], "URIs match" );
+}
+
+Test::IData->setup;
+
+{
+    # key
+    my $uri1 = $idata->create_object( "foo" );
+    my $uri2 = $idata->replicate_object( "foo" );
+
+    my $locations = $idata->find_instances( "foo" );
+
+    ok( eq_set( [$uri1, $uri2], $locations ), "URIs match" );
+}
+
+Test::IData->setup;
+
+{
+    # key, volume
+    my $uri = $idata->create_object( "foo" );
+
+    my $locations = $idata->find_instances( "foo", "node01" );
+
+    is( $uri, $locations->[0], "URIs match" );
+}
+
+Test::IData->setup;
+
+{
+    # key
+    my $uri1 = $idata->create_object( "foo" );
+    my $uri2 = $idata->replicate_object( "foo" );
+
+    my $locations = $idata->find_instances( "foo", "node01" );
+
+    ok( eq_set( [$uri1, $uri2], $locations ), "URIs match" );
+}
+
+
+Test::IData->setup;
+
+eval {
+    $idata->find_instances( "foo" );
+};
+like( $@, qr/no instances found/, "storage object does not exist" );
+
+Test::IData->setup;
+
+eval {
+    $idata->find_instances();
+};
+like( $@, qr/1 - 2 were expected/, "no params" );
+
+Test::IData->setup;
+
+eval {
+    $idata->find_instances( "foo", 'read', 3 );
+};
+like( $@, qr/1 - 2 were expected/, "too many params" );
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/t/08_server_delete_instance.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/t/08_server_delete_instance.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/t/08_server_delete_instance.t	(revision 22254)
@@ -0,0 +1,71 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 8;
+
+use lib qw( ./t ./lib );
+
+use PS::IPP::IData::Server;
+use PS::IPP::IData::Util qw( :standard );
+use Test::IData;
+
+my $idata = "PS::IPP::IData::Server";
+
+$idata->setup(
+    "DBI:mysql:database=test:host=localhost",
+    "test",
+    "",
+);
+
+Test::IData->setup;
+
+{
+    my $uri = $idata->create_object( "foo" );
+
+    ok( $idata->delete_instance( $uri ), "delete instance" );
+}
+
+Test::IData->setup;
+
+{
+    my $uri1 = $idata->create_object( "foo" );
+    my $uri2 = $idata->replicate_object( "foo" );
+
+    ok( $idata->delete_instance( $uri1 ), "delete instance" );
+
+    my $locations = $idata->find_instances( "foo" );
+
+    is( $locations->[0], $uri2, "instance remains" );
+
+    ok( $idata->delete_instance( $uri2 ), "delete instance" );
+
+    eval {
+        $idata->find_instances( "foo" );
+    };
+    like( $@, qr/no instances found/, "storage object was deleted" );
+}
+
+Test::IData->setup;
+
+eval {
+    $idata->delete_instance( "file:/foo" );
+};
+like( $@, qr/no instance is associated with uri/, "uri does not exist" );
+
+Test::IData->setup;
+
+eval {
+    $idata->delete_instance();
+};
+like( $@, qr/1 was expected/, "no params" );
+
+Test::IData->setup;
+
+eval {
+    $idata->delete_instance( "foo", 2 );
+};
+like( $@, qr/1 was expected/, "too many params" );
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/t/09_server_stat_object.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/t/09_server_stat_object.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/t/09_server_stat_object.t	(revision 22254)
@@ -0,0 +1,70 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 12;
+
+use lib qw( ./t ./lib );
+
+use PS::IPP::IData::Server;
+use PS::IPP::IData::Util qw( :standard );
+use Test::IData;
+
+my $idata = "PS::IPP::IData::Server";
+
+$idata->setup(
+    "DBI:mysql:database=test:host=localhost",
+    "test",
+    "",
+);
+
+Test::IData->setup;
+
+{
+    my $uri = $idata->create_object( "foo" );
+
+    my $info = $idata->stat_object( "foo" );
+
+    is( scalar @$info, 7, "number of columns" );
+}
+
+Test::IData->setup;
+
+{
+    my $uri = $idata->create_object( "foo", 9, "node01", "foobar" );
+
+    my $info = $idata->stat_object( "foo" );
+
+    is( scalar @$info, 7,                       "number of columns" );
+    is( @$info[0], 1,                           "so_id" );
+    is( @$info[1], "foo",                       "ext_id" );
+    is( @$info[2], 9,                           "class_id" );
+    is( @$info[3], "foobar",                    "comment" );
+    is( @$info[4], 0,                           "read lock" );
+    is( @$info[5], undef,                       "write lock" );
+    like( @$info[6], qr/....-..-.. ..:..:../,   "epoch" );
+}
+
+Test::IData->setup;
+
+eval {
+    $idata->stat_object( "foo" );
+};
+like( $@, qr/no storage object found/, "object does not exist" );
+
+Test::IData->setup;
+
+eval {
+    $idata->stat_object();
+};
+like( $@, qr/1 was expected/, "no params" );
+
+Test::IData->setup;
+
+eval {
+    $idata->stat_object( "foo", 2 );
+};
+like( $@, qr/1 was expected/, "too many params" );
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/t/10_client_new.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/t/10_client_new.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/t/10_client_new.t	(revision 22254)
@@ -0,0 +1,56 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Apache::Test qw( -withtestmore );
+
+plan tests => 4;
+
+use lib qw( ./t ./lib );
+
+use PS::IPP::IData::Client;
+use PS::IPP::IData::Util qw( :standard );
+use Test::IData;
+
+my $hostport = Apache::Test->config->{ 'hostport' };
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    isa_ok( $idata, "PS::IPP::IData::Client" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+        uri   => "http://example.com/PS/IPP/IData/Client",
+    );
+
+    isa_ok( $idata, "PS::IPP::IData::Client" );
+}
+
+Test::IData->setup;
+
+eval {
+    PS::IPP::IData::Client->new;
+};
+like( $@, qr/Mandatory parameter/, "no proxy" );
+
+Test::IData->setup;
+
+eval {
+    PS::IPP::IData::Client->new(
+        proxy => "foo",
+        dog => "do"
+    );
+};
+like( $@, qr/not listed in the validation options/, "bad param" );
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/t/11_client_create.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/t/11_client_create.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/t/11_client_create.t	(revision 22254)
@@ -0,0 +1,103 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Apache::Test qw( -withtestmore );
+
+plan tests => 7;
+
+use lib qw( ./t ./lib );
+
+use PS::IPP::IData::Client;
+use PS::IPP::IData::Util qw( :standard );
+use Test::IData;
+
+my $hostport = Apache::Test->config->{ 'hostport' };
+
+Test::IData->setup;
+
+{
+    # key
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    my $fh = $idata->create( "foo" );
+
+    is( ref $fh, 'GLOB', "good filehandle" );
+}
+
+Test::IData->setup;
+
+{
+    # key, class
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    my $fh = $idata->create( "foo", 0 );
+
+    is( ref $fh, 'GLOB', "good filehandle" );
+}
+
+Test::IData->setup;
+
+{
+    # key, class, volume
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    my $fh = $idata->create( "foo", 0, "node01" );
+
+    is( ref $fh, 'GLOB', "good filehandle" );
+}
+
+Test::IData->setup;
+
+{
+    # key, class, volume, comment
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    my $fh = $idata->create( "foo", 0, "node01", "this is foo" );
+
+    is( ref $fh, 'GLOB', "good filehandle" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    $idata->create( "foo" );
+    is( $idata->create( "foo" ), undef, "object already exists" );
+}
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    $idata->create();
+};
+like( $@, qr/2 - 4 were expected/, "no params" );
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    $idata->create( 1, 2, 3, 4, 5 );
+};
+like( $@, qr/2 - 4 were expected/, "too many params" );
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/t/12_client_replicate.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/t/12_client_replicate.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/t/12_client_replicate.t	(revision 22254)
@@ -0,0 +1,97 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Apache::Test qw( -withtestmore );
+
+plan tests => 11;
+
+use lib qw( ./t ./lib );
+
+use PS::IPP::IData::Client;
+use PS::IPP::IData::Util qw( :standard );
+use Test::IData;
+
+my $hostport = Apache::Test->config->{ 'hostport' };
+
+Test::IData->setup;
+
+{
+    # key
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    my $uri = $idata->replicate( "foo" );
+
+    ok( $uri, "good replication" );
+    ok( -e _get_file_path( $uri ), "file exists" );
+}
+
+Test::IData->setup;
+
+{
+    # key, $volume
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    my $uri = $idata->replicate( "foo", "node01" );
+
+    ok( $uri, "good replication" );
+    ok( -e _get_file_path( $uri ), "file exists" );
+}
+
+Test::IData->setup;
+
+{
+    # key, $volume
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    my $uri1 = $idata->replicate( "foo", "node01" );
+    my $uri2 = $idata->replicate( "foo", "node02" );
+
+    ok( $uri1, "good replication" );
+    ok( $uri2, "good replication" );
+    ok( -e _get_file_path( $uri1 ), "file exists" );
+    ok( -e _get_file_path( $uri2 ), "file exists" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    my $uri = $idata->replicate( "foo" );
+
+    is( $uri, undef, "storage object does not exist" );
+}
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->replicate();
+};
+like( $@, qr/1 - 2 were expected/, "no params" );
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->replicate( 1, 2, 3 );
+};
+like( $@, qr/1 - 2 were expected/, "too many params" );
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/t/13_client_cull.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/t/13_client_cull.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/t/13_client_cull.t	(revision 22254)
@@ -0,0 +1,127 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Apache::Test qw( -withtestmore );
+
+plan tests => 15;
+
+use lib qw( ./t ./lib );
+
+use PS::IPP::IData::Client;
+use PS::IPP::IData::Util qw( :standard );
+use Test::IData;
+
+my $hostport = Apache::Test->config->{ 'hostport' };
+
+Test::IData->setup;
+
+{
+    # key
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+    $idata->replicate( "foo" );
+
+    my $uri = $idata->cull( "foo" );
+
+    ok( $uri, "good cull" );
+    ok( ! -e _get_file_path( $uri ), "file doesn't exist" );
+}
+
+Test::IData->setup;
+
+SKIP: {
+    skip "unsure if this should be part of the API", 2;
+
+    # key, $volume
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+    $idata->replicate( "foo", "node01" );
+
+    my $uri = $idata->cull( "foo", "node01" );
+
+    ok( $uri, "good cull" );
+    ok( ! -e _get_file_path( $uri ), "file exists" );
+}
+
+Test::IData->setup;
+
+{
+    # key
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    $idata->replicate( "foo", "node01" );
+    $idata->replicate( "foo", "node02" );
+
+    my $uri1 = $idata->cull( "foo" );
+    my $uri2 = $idata->cull( "foo" );
+
+    ok( $uri1, "good cull" );
+    ok( $uri2, "good cull" );
+    ok( ! -e _get_file_path( $uri1 ), "file exists" );
+    ok( ! -e _get_file_path( $uri2 ), "file exists" );
+}
+
+Test::IData->setup;
+
+SKIP: {
+    skip "unsure if this should be part of the API", 4;
+
+    # key, $volume
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    $idata->replicate( "foo", "node01" );
+    $idata->replicate( "foo", "node02" );
+
+    my $uri1 = $idata->cull( "foo", "node01" );
+    my $uri2 = $idata->cull( "foo", "node02" );
+
+    ok( $uri1, "good cull" );
+    ok( $uri2, "good cull" );
+    ok( ! -e _get_file_path( $uri1 ), "file exists" );
+    ok( ! -e _get_file_path( $uri2 ), "file exists" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    my $uri = $idata->cull( "foo" );
+
+    is( $uri, undef, "storage object does not exist" );
+}
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->cull();
+};
+like( $@, qr/1 was expected/, "no params" );
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->cull( 1, 2 );
+};
+like( $@, qr/1 was expected/, "too many params" );
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/t/14_client_lock.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/t/14_client_lock.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/t/14_client_lock.t	(revision 22254)
@@ -0,0 +1,269 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Apache::Test qw( -withtestmore );
+
+plan tests => 19;
+
+use lib qw( ./t ./lib );
+
+use PS::IPP::IData::Client;
+use PS::IPP::IData::Util qw( :standard );
+use Test::IData;
+
+my $hostport = Apache::Test->config->{ 'hostport' };
+
+Test::IData->setup;
+
+{
+    # key, type
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    ok( $idata->lock( "foo", "read" ), "read lock" );
+}
+
+Test::IData->setup;
+
+{
+    # key, type, timeout
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    ok( $idata->lock( "foo", "read", 10 ), "read lock" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    ok( $idata->lock( "foo", "read" ), "read lock" );
+    ok( $idata->lock( "foo", "read" ), "read lock" );
+}
+
+Test::IData->setup;
+
+{
+    # key, type
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    ok( $idata->lock( "foo", "write" ), "write lock" );
+}
+
+Test::IData->setup;
+
+{
+    # key, type, timeout
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    ok( $idata->lock( "foo", "write", 10 ), "write lock" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    is( $idata->lock( "foo", "read" ), undef, "storage object does not exist" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    is( $idata->lock( "foo", "write" ), undef, "storage object does not exist" );
+}
+
+Test::IData->setup;
+
+diag "testing a spinlock, this will be a litle slow...";
+
+eval {
+    local $SIG{ALRM} = sub { alarm 0; die "timeout"; };
+    alarm 8;
+
+    # key, type, timeout
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    $idata->lock( "foo", "read" );
+    $idata->lock( "foo", "write" );
+
+    alarm 0;
+};
+like( $@, qr/timeout/, "default spinlock" );
+
+Test::IData->setup;
+
+diag "testing a spinlock, this will be a litle slow...";
+
+eval {
+    local $SIG{ALRM} = sub { alarm 0; die "timeout"; };
+    alarm 12;
+
+    # key, type, timeout
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    $idata->lock( "foo", "read" );
+    unless ( $idata->lock( "foo", "write" ) ) {
+        alarm 0;
+        die "gaveup";
+    }
+
+    alarm 0;
+};
+like( $@, qr/gaveup/, "default spinlock" );
+
+Test::IData->setup;
+
+diag "testing a spinlock, this will be a litle slow...";
+
+eval {
+    local $SIG{ALRM} = sub { alarm 0; die "timeout"; };
+    alarm 3;
+
+    # key, type, timeout
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    $idata->lock( "foo", "read" );
+    $idata->lock( "foo", "write", 5 );
+
+    alarm 0;
+};
+like( $@, qr/timeout/, "specified spinlock" );
+
+Test::IData->setup;
+
+diag "testing a spinlock, this will be a litle slow...";
+
+eval {
+    local $SIG{ALRM} = sub { alarm 0; die "timeout"; };
+    alarm 7;
+
+    # key, type, timeout
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    $idata->lock( "foo", "read" );
+    unless ( $idata->lock( "foo", "write", 5 ) ) {
+        alarm 0;
+        die "gaveup";
+    }
+
+    alarm 0;
+};
+
+like( $@, qr/gaveup/, "specified spinlock" );
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    $idata->lock( "foo", "write" );
+    is( $idata->lock( "foo", "write", 1 ), undef, "can not write lock twice" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    $idata->lock( "foo", "read" );
+    is( $idata->lock( "foo", "write", 1 ), undef, "can not write lock after read lock" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    $idata->lock( "foo", "write" );
+    is( $idata->lock( "foo", "read", 1 ), undef, "can not read lock after write lock" );
+}
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    $idata->lock();
+};
+like( $@, qr/2 - 3 were expected/, "no params" );
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    $idata->lock( "foo" );
+};
+like( $@, qr/2 - 3 were expected/, "not enough params" );
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    $idata->lock( "foo", "both" );
+};
+like( $@, qr/is read or write/, "not read or write" );
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    $idata->lock( "foo", 'read', 3, "bar" );
+};
+like( $@, qr/2 - 3 were expected/, "too many params" );
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/t/15_client_unlock.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/t/15_client_unlock.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/t/15_client_unlock.t	(revision 22254)
@@ -0,0 +1,166 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Apache::Test qw( -withtestmore );
+
+plan tests => 14;
+
+use lib qw( ./t ./lib );
+
+use PS::IPP::IData::Client;
+use PS::IPP::IData::Util qw( :standard );
+use Test::IData;
+
+my $hostport = Apache::Test->config->{ 'hostport' };
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+    $idata->lock( "foo", "read" );
+
+    ok( $idata->unlock( "foo", "read" ), "read unlock" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+    $idata->lock( "foo", "read" );
+    $idata->lock( "foo", "read" );
+
+    ok( $idata->unlock( "foo", "read" ), "read unlock" );
+    ok( $idata->unlock( "foo", "read" ), "read unlock" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+    $idata->lock( "foo", "write" );
+
+    ok( $idata->unlock( "foo", "write" ), "write unlock" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    is( $idata->unlock( "foo", "read" ), undef, "storage object does not exist" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    is( $idata->unlock( "foo", "write" ), undef, "storage object does not exist" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    is( $idata->unlock( "foo", "read" ), undef, "no lock set" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    is( $idata->unlock( "foo", "write" ), undef, "no lock set" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+    $idata->lock( "foo", "write" );
+
+    is( $idata->unlock( "foo", "read" ), undef, "read unlock under write lock" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+    $idata->lock( "foo", "read" );
+
+    is( $idata->unlock( "foo", "write" ), undef, "write unlock under read lock" );
+}
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    $idata->unlock();
+};
+like( $@, qr/2 were expected/, "no params" );
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    $idata->unlock( "foo" );
+};
+like( $@, qr/2 were expected/, "not enough params" );
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    $idata->unlock( "foo", "both" );
+};
+like( $@, qr/is read or write/, "not read or write" );
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    $idata->unlock( "foo", 'read', 3 );
+};
+like( $@, qr/2 were expected/, "too many params" );
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/t/16_client_find_instances.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/t/16_client_find_instances.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/t/16_client_find_instances.t	(revision 22254)
@@ -0,0 +1,114 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Apache::Test qw( -withtestmore );
+
+plan tests => 7;
+
+use lib qw( ./t ./lib );
+
+use PS::IPP::IData::Client;
+use PS::IPP::IData::Util qw( :standard );
+use Test::IData;
+
+my $hostport = Apache::Test->config->{ 'hostport' };
+
+Test::IData->setup;
+
+SKIP: {
+    skip "need to write a module to validate URIs", 7;
+
+{
+    # key
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    my $uri = $idata->create( "foo" );
+
+    my $locations = $idata->find_instances( "foo" );
+
+    is( $uri, @$locations[0], "URIs match" );
+}
+
+Test::IData->setup;
+
+{
+    # key
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    my $uri1 = $idata->create( "foo" );
+    my $uri2 = $idata->replicate( "foo" );
+
+    my $locations = $idata->find_instances( "foo" );
+
+    ok( eq_set( [$uri1, $uri2], $locations ), "URIs match" );
+}
+
+Test::IData->setup;
+
+{
+    # key, volume
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    my $uri = $idata->create( "foo" );
+
+    my $locations = $idata->find_instances( "foo", "node01" );
+
+    is( $uri, $locations->[0], "URIs match" );
+}
+
+Test::IData->setup;
+
+{
+    # key
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    my $uri1 = $idata->create( "foo" );
+    my $uri2 = $idata->replicate( "foo" );
+
+    my $locations = $idata->find_instances( "foo", "node01" );
+
+    ok( eq_set( [$uri1, $uri2], $locations ), "URIs match" );
+}
+
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    is( $idata->find_instances( "foo" ), undef, "storage object does not exist" );
+}
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    $idata->find_instances();
+};
+like( $@, qr/1 - 2 were expected/, "no params" );
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    $idata->find_instances( "foo", 'read', 3 );
+};
+like( $@, qr/1 - 2 were expected/, "too many params" );
+
+Test::IData->cleanup;
+
+}
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/t/apache_03_server_create_object.x
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/t/apache_03_server_create_object.x	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/t/apache_03_server_create_object.x	(revision 22254)
@@ -0,0 +1,95 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Apache::Test qw(-withtestmore);
+
+plan tests => 11;
+
+use lib qw( ./t ./lib );
+
+use Apache::TestUtil;
+use PS::IPP::IData::Util qw( :standard );
+use SOAP::Lite;
+use Test::IData;
+
+my $hostport = Apache::Test::config()->{ 'hostport' };
+my $idata = SOAP::Lite->new(
+    proxy   => "http://$hostport/idata",
+    uri     => "http://pan-starrs.ifa.hawaii.edu/PS/IPP/IData/Server",
+);
+
+Test::IData->setup;
+
+{
+    # key
+    my $uri = $idata->create_object( "foo" )->result;
+
+    ok( -e _get_file_path( $uri ), "file exists" );
+
+    like( $uri, qr/^file:\//, "valid URI" );
+}
+
+Test::IData->setup;
+
+{
+    # key, class
+    my $uri = $idata->create_object( "foo", 0 )->result;
+    $idata->delete_instance( $uri );
+
+    ok( -e _get_file_path( $uri ), "file exists" );
+
+    like( $uri, qr/^file:\//, "valid URI" );
+}
+
+Test::IData->setup;
+
+{
+    # key, class, volume
+    my $uri = $idata->create_object( "foo", 0, "node01" )->result;
+    $idata->delete_instance( $uri );
+
+    ok( -e _get_file_path( $uri ), "file exists" );
+
+    like( $uri, qr/^file:\//, "valid URI" );
+}
+
+Test::IData->setup;
+
+{
+    # key, class, volume, comment
+    my $uri = $idata->create_object( "foo", 0, "node01", "this is foo" )->result;
+    $idata->delete_instance( $uri );
+
+    ok( -e _get_file_path( $uri ), "file exists" );
+
+    like( $uri, qr/^file:\//, "valid URI" );
+}
+
+Test::IData->setup;
+
+{
+    $idata->create_object( "foo" );
+    my $response = $idata->create_object( "foo" );
+
+    like( $response->faultstring, qr/Duplicate entry/, "object already exists" );
+}
+
+Test::IData->setup;
+
+{
+    my $response = $idata->create_object();
+
+    like( $response->faultstring, qr/2 - 4 were expected/, "no params" );
+}
+
+Test::IData->setup;
+
+{
+    my $response = $idata->create_object( 1, 2, 3, 4, 5 );
+
+    like( $response->faultstring, qr/2 - 4 were expected/, "too many params" );
+}
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/t/apache_04_server_replicate_object.x
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/t/apache_04_server_replicate_object.x	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/t/apache_04_server_replicate_object.x	(revision 22254)
@@ -0,0 +1,86 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Apache::Test qw(-withtestmore);
+
+plan tests => 11;
+
+use lib qw( ./t ./lib );
+
+use Apache::TestUtil;
+use PS::IPP::IData::Util qw( :standard );
+use SOAP::Lite;
+use Test::IData;
+
+my $hostport = Apache::Test::config()->{ 'hostport' };
+my $idata = SOAP::Lite->new(
+    proxy   => "http://$hostport/idata",
+    uri     => "http://pan-starrs.ifa.hawaii.edu/PS/IPP/IData/Server",
+);
+
+Test::IData->setup;
+
+{
+    # key
+    $idata->create_object( "foo" );
+    my $uri = $idata->replicate_object( "foo" )->result;
+
+    ok( -e _get_file_path( $uri ), "file exists" );
+
+    like( $uri, qr/^file:\//, "valid URI" );
+}
+
+Test::IData->setup;
+
+{
+    # key, $volume
+    $idata->create_object( "foo" );
+    my $uri = $idata->replicate_object( "foo", "node01" )->result;
+
+    ok( -e _get_file_path( $uri ), "file exists" );
+
+    like( $uri, qr/^file:\//, "valid URI" );
+}
+
+Test::IData->setup;
+
+{
+    # key, $volume
+    $idata->create_object( "foo" );
+    my $uri1 = $idata->replicate_object( "foo", "node01" )->result;
+    my $uri2 = $idata->replicate_object( "foo", "node02" )->result;
+
+    ok( -e _get_file_path( $uri1 ), "file exists" );
+    ok( -e _get_file_path( $uri2 ), "file exists" );
+
+    like( $uri1, qr/^file:\//, "valid URI" );
+    like( $uri2, qr/^file:\//, "valid URI" );
+}
+
+Test::IData->setup;
+
+{
+    my $response = $idata->replicate_object();
+
+    like( $response->faultstring, qr/1 - 2 were expected/, "no params" );
+}
+
+Test::IData->setup;
+
+{
+    my $response = $idata->replicate_object( 1, 2, 3 );
+
+    like( $response->faultstring, qr/1 - 2 were expected/, "too many params" );
+}
+
+Test::IData->setup;
+
+{
+    my $response = $idata->replicate_object( "foo" );
+
+    like( $response->faultstring, qr/storage object does not exist/, "storage object does not exist" );
+}
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous-Server/t/apache_05_server_lock_object.x
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous-Server/t/apache_05_server_lock_object.x	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous-Server/t/apache_05_server_lock_object.x	(revision 22254)
@@ -0,0 +1,129 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Apache::Test qw(-withtestmore);
+
+plan tests => 13;
+
+use lib qw( ./t ./lib );
+
+use Apache::TestUtil;
+use PS::IPP::IData::Util qw( :standard );
+use SOAP::Lite;
+use Test::IData;
+
+my $hostport = Apache::Test::config()->{ 'hostport' };
+my $idata = SOAP::Lite->new(
+    proxy   => "http://$hostport/idata",
+    uri     => "http://pan-starrs.ifa.hawaii.edu/PS/IPP/IData/Server",
+);
+
+Test::IData->setup;
+
+{
+    $idata->create_object( "foo" );
+
+    ok( $idata->lock_object( "foo", "read" )->result, "read lock" );
+}
+
+Test::IData->setup;
+
+{
+    $idata->create_object( "foo" );
+
+    ok( $idata->lock_object( "foo", "read" )->result, "read lock" );
+    ok( $idata->lock_object( "foo", "read" )->result, "read lock" );
+}
+
+Test::IData->setup;
+
+{
+    $idata->create_object( "foo" );
+
+    ok( $idata->lock_object( "foo", "write" )->result, "write lock" );
+}
+
+Test::IData->setup;
+
+{
+    my $result = $idata->lock_object( "foo", "read" );
+
+    like( $result->faultstring, qr/storage object does not exist/, "storage object does not exist" );
+}
+
+Test::IData->setup;
+
+{
+    my $result = $idata->lock_object( "foo", "write" );
+
+    like( $result->faultstring, qr/storage object does not exist/, "storage object does not exist" );
+}
+
+Test::IData->setup;
+
+{
+    $idata->create_object( "foo" );
+
+    $idata->lock_object( "foo", "write" );
+    my $result = $idata->lock_object( "foo", "write" );
+
+    like( $result->faultstring, qr/can not write lock twice/, "can not write lock twice" );
+}
+
+Test::IData->setup;
+
+{
+    $idata->create_object( "foo" );
+
+    $idata->lock_object( "foo", "read" );
+    my $result = $idata->lock_object( "foo", "write" );
+
+    like( $result->faultstring, qr/can not write lock after read lock/, "can not write lock after read lock" );
+}
+
+Test::IData->setup;
+
+{
+    $idata->create_object( "foo" );
+
+    $idata->lock_object( "foo", "write" );
+    my $result = $idata->lock_object( "foo", "read" );
+
+    like( $result->faultstring, qr/can not read lock after write lock/, "can not read lock after write lock" );
+}
+
+Test::IData->setup;
+
+{
+    my $result = $idata->lock_object();
+
+    like( $result->faultstring, qr/2 were expected/, "no params" );
+}
+
+Test::IData->setup;
+
+{
+    my $result = $idata->lock_object( "foo" );
+
+    like( $result->faultstring, qr/2 were expected/, "not enough params" );
+}
+
+Test::IData->setup;
+
+{
+    my $result = $idata->lock_object( "foo", "both" );
+
+    like( $result->faultstring, qr/is read or write/, "not read or write" );
+}
+
+Test::IData->setup;
+
+{
+    my $result = $idata->lock_object( "foo", 'read', 3 );
+
+    like( $result->faultstring, qr/2 were expected/, "too many params" );
+}
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous/Build.PL
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/Build.PL	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/Build.PL	(revision 22254)
@@ -0,0 +1,30 @@
+use Module::Build;
+# See perldoc Module::Build for details of how this works
+
+my $build_pkg   = eval { require Apache::TestMB }
+                ? 'Apache::TestMB'
+                : 'Module::Build';
+
+$build_pkg->new(
+    module_name         => 'PS::IPP::IData',
+    dist_version_from   => 'lib/PS/IPP/IData/Server.pm',
+    author              => 'Joshua Hoblitt <jhoblitt@cpan.org>',
+    license             => 'gpl',
+    requires            => {
+        'URI'                   => 1.30,
+        'SOAP::Lite'            => 0.60,
+        'Log::Log4perl'         => 0.48,
+        'DBI'                   => 1.42,
+        'DBD::mysql'            => 2.9004,
+        'Class::Accessor::Fast' => 0,
+        'Params::Validate'      => 0,
+        'Apache::Test'          => 1.12,
+        'Test::More'            => 0.49,
+    },
+    recommends          => {
+        'Apache::DBI'           => 0.94,
+    },
+    build_requires      => {
+        'File::Temp'            => 0,
+    },
+)->create_build_script;
Index: /branches/jh_branches/jhoblitt/Nebulous/Changes
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/Changes	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/Changes	(revision 22254)
@@ -0,0 +1,5 @@
+Revision history for Perl module Poi::PixelData
+
+0.01 Fri Sep  3 10:23:31 2004
+    - first working version
+	- original version; created by ExtUtils::ModuleMaker 0.32
Index: /branches/jh_branches/jhoblitt/Nebulous/LICENSE
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/LICENSE	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/LICENSE	(revision 22254)
@@ -0,0 +1,260 @@
+The General Public License (GPL)
+Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave,
+Cambridge, MA 02139, USA. Everyone is permitted to copy and distribute
+verbatim copies of this license document, but changing it is not allowed.
+
+Preamble
+
+The licenses for most software are designed to take away your freedom to share
+and change it. By contrast, the GNU General Public License is intended to
+guarantee your freedom to share and change free software--to make sure the
+software is free for all its users. This General Public License applies to most of
+the Free Software Foundation's software and to any other program whose
+authors commit to using it. (Some other Free Software Foundation software is
+covered by the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+When we speak of free software, we are referring to freedom, not price. Our
+General Public Licenses are designed to make sure that you have the freedom
+to distribute copies of free software (and charge for this service if you wish), that
+you receive source code or can get it if you want it, that you can change the
+software or use pieces of it in new free programs; and that you know you can do
+these things.
+
+To protect your rights, we need to make restrictions that forbid anyone to deny
+you these rights or to ask you to surrender the rights. These restrictions
+translate to certain responsibilities for you if you distribute copies of the
+software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether gratis or for a
+fee, you must give the recipients all the rights that you have. You must make
+sure that they, too, receive or can get the source code. And you must show
+them these terms so they know their rights.
+
+We protect your rights with two steps: (1) copyright the software, and (2) offer
+you this license which gives you legal permission to copy, distribute and/or
+modify the software.
+
+Also, for each author's protection and ours, we want to make certain that
+everyone understands that there is no warranty for this free software. If the
+software is modified by someone else and passed on, we want its recipients to
+know that what they have is not the original, so that any problems introduced by
+others will not reflect on the original authors' reputations.
+
+Finally, any free program is threatened constantly by software patents. We wish
+to avoid the danger that redistributors of a free program will individually obtain
+patent licenses, in effect making the program proprietary. To prevent this, we
+have made it clear that any patent must be licensed for everyone's free use or
+not licensed at all.
+
+The precise terms and conditions for copying, distribution and modification
+follow.
+
+GNU GENERAL PUBLIC LICENSE
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND
+MODIFICATION
+
+0. This License applies to any program or other work which contains a notice
+placed by the copyright holder saying it may be distributed under the terms of
+this General Public License. The "Program", below, refers to any such program
+or work, and a "work based on the Program" means either the Program or any
+derivative work under copyright law: that is to say, a work containing the
+Program or a portion of it, either verbatim or with modifications and/or translated
+into another language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not covered by
+this License; they are outside its scope. The act of running the Program is not
+restricted, and the output from the Program is covered only if its contents
+constitute a work based on the Program (independent of having been made by
+running the Program). Whether that is true depends on what the Program does.
+
+1. You may copy and distribute verbatim copies of the Program's source code as
+you receive it, in any medium, provided that you conspicuously and appropriately
+publish on each copy an appropriate copyright notice and disclaimer of warranty;
+keep intact all the notices that refer to this License and to the absence of any
+warranty; and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and you may at
+your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Program or any portion of it, thus
+forming a work based on the Program, and copy and distribute such
+modifications or work under the terms of Section 1 above, provided that you also
+meet all of these conditions:
+
+a) You must cause the modified files to carry prominent notices stating that you
+changed the files and the date of any change.
+
+b) You must cause any work that you distribute or publish, that in whole or in
+part contains or is derived from the Program or any part thereof, to be licensed
+as a whole at no charge to all third parties under the terms of this License.
+
+c) If the modified program normally reads commands interactively when run, you
+must cause it, when started running for such interactive use in the most ordinary
+way, to print or display an announcement including an appropriate copyright
+notice and a notice that there is no warranty (or else, saying that you provide a
+warranty) and that users may redistribute the program under these conditions,
+and telling the user how to view a copy of this License. (Exception: if the
+Program itself is interactive but does not normally print such an announcement,
+your work based on the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If identifiable
+sections of that work are not derived from the Program, and can be reasonably
+considered independent and separate works in themselves, then this License,
+and its terms, do not apply to those sections when you distribute them as
+separate works. But when you distribute the same sections as part of a whole
+which is a work based on the Program, the distribution of the whole must be on
+the terms of this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest your rights to
+work written entirely by you; rather, the intent is to exercise the right to control
+the distribution of derivative or collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program with the
+Program (or with a work based on the Program) on a volume of a storage or
+distribution medium does not bring the other work under the scope of this
+License.
+
+3. You may copy and distribute the Program (or a work based on it, under
+Section 2) in object code or executable form under the terms of Sections 1 and 2
+above provided that you also do one of the following:
+
+a) Accompany it with the complete corresponding machine-readable source
+code, which must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange; or,
+
+b) Accompany it with a written offer, valid for at least three years, to give any
+third party, for a charge no more than your cost of physically performing source
+distribution, a complete machine-readable copy of the corresponding source
+code, to be distributed under the terms of Sections 1 and 2 above on a medium
+customarily used for software interchange; or,
+
+c) Accompany it with the information you received as to the offer to distribute
+corresponding source code. (This alternative is allowed only for noncommercial
+distribution and only if you received the program in object code or executable
+form with such an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for making
+modifications to it. For an executable work, complete source code means all the
+source code for all modules it contains, plus any associated interface definition
+files, plus the scripts used to control compilation and installation of the
+executable. However, as a special exception, the source code distributed need
+not include anything that is normally distributed (in either source or binary form)
+with the major components (compiler, kernel, and so on) of the operating system
+on which the executable runs, unless that component itself accompanies the
+executable.
+
+If distribution of executable or object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the source
+code from the same place counts as distribution of the source code, even though
+third parties are not compelled to copy the source along with the object code.
+
+4. You may not copy, modify, sublicense, or distribute the Program except as
+expressly provided under this License. Any attempt otherwise to copy, modify,
+sublicense or distribute the Program is void, and will automatically terminate
+your rights under this License. However, parties who have received copies, or
+rights, from you under this License will not have their licenses terminated so long
+as such parties remain in full compliance.
+
+5. You are not required to accept this License, since you have not signed it.
+However, nothing else grants you permission to modify or distribute the Program
+or its derivative works. These actions are prohibited by law if you do not accept
+this License. Therefore, by modifying or distributing the Program (or any work
+based on the Program), you indicate your acceptance of this License to do so,
+and all its terms and conditions for copying, distributing or modifying the
+Program or works based on it.
+
+6. Each time you redistribute the Program (or any work based on the Program),
+the recipient automatically receives a license from the original licensor to copy,
+distribute or modify the Program subject to these terms and conditions. You
+may not impose any further restrictions on the recipients' exercise of the rights
+granted herein. You are not responsible for enforcing compliance by third parties
+to this License.
+
+7. If, as a consequence of a court judgment or allegation of patent infringement
+or for any other reason (not limited to patent issues), conditions are imposed on
+you (whether by court order, agreement or otherwise) that contradict the
+conditions of this License, they do not excuse you from the conditions of this
+License. If you cannot distribute so as to satisfy simultaneously your obligations
+under this License and any other pertinent obligations, then as a consequence
+you may not distribute the Program at all. For example, if a patent license would
+not permit royalty-free redistribution of the Program by all those who receive
+copies directly or indirectly through you, then the only way you could satisfy
+both it and this License would be to refrain entirely from distribution of the
+Program.
+
+If any portion of this section is held invalid or unenforceable under any particular
+circumstance, the balance of the section is intended to apply and the section as
+a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any patents or other
+property right claims or to contest validity of any such claims; this section has
+the sole purpose of protecting the integrity of the free software distribution
+system, which is implemented by public license practices. Many people have
+made generous contributions to the wide range of software distributed through
+that system in reliance on consistent application of that system; it is up to the
+author/donor to decide if he or she is willing to distribute software through any
+other system and a licensee cannot impose that choice.
+
+This section is intended to make thoroughly clear what is believed to be a
+consequence of the rest of this License.
+
+8. If the distribution and/or use of the Program is restricted in certain countries
+either by patents or by copyrighted interfaces, the original copyright holder who
+places the Program under this License may add an explicit geographical
+distribution limitation excluding those countries, so that distribution is permitted
+only in or among countries not thus excluded. In such case, this License
+incorporates the limitation as if written in the body of this License.
+
+9. The Free Software Foundation may publish revised and/or new versions of the
+General Public License from time to time. Such new versions will be similar in
+spirit to the present version, but may differ in detail to address new problems or
+concerns.
+
+Each version is given a distinguishing version number. If the Program specifies a
+version number of this License which applies to it and "any later version", you
+have the option of following the terms and conditions either of that version or of
+any later version published by the Free Software Foundation. If the Program does
+not specify a version number of this License, you may choose any version ever
+published by the Free Software Foundation.
+
+10. If you wish to incorporate parts of the Program into other free programs
+whose distribution conditions are different, write to the author to ask for
+permission. For software which is copyrighted by the Free Software Foundation,
+write to the Free Software Foundation; we sometimes make exceptions for this.
+Our decision will be guided by the two goals of preserving the free status of all
+derivatives of our free software and of promoting the sharing and reuse of
+software generally.
+
+NO WARRANTY
+
+11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS
+NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE
+COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM
+"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR
+IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
+ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE,
+YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
+CORRECTION.
+
+12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED
+TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY
+WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS
+PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
+ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM
+(INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY
+OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS
+BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+END OF TERMS AND CONDITIONS
Index: /branches/jh_branches/jhoblitt/Nebulous/MANIFEST
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/MANIFEST	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/MANIFEST	(revision 22254)
@@ -0,0 +1,53 @@
+Build.PL
+Changes
+LICENSE
+MANIFEST
+META.yml
+Makefile.PL
+README
+TODO
+Todo
+bootstrap.sh
+docs/apache_setup.txt
+docs/c_api.h
+docs/calc_gid.c
+docs/database_setup.txt
+docs/design.txt
+docs/requirements.txt
+docs/setup.txt
+docs/tmp.txt
+iData.wsdl
+lib/PS/IPP/IData/Client.pm
+lib/PS/IPP/IData/Client/HTTP.pm
+lib/PS/IPP/IData/Client/Log.pm
+lib/PS/IPP/IData/Server.pm
+lib/PS/IPP/IData/Server/Apache.pm
+lib/PS/IPP/IData/Server/Log.pm
+lib/PS/IPP/IData/Server/SQL.pm
+lib/PS/IPP/IData/Util.pm
+scripts/bench_test.pl
+scripts/imageserver.pl
+scripts/ptest.pl
+scripts/stest.pl
+t/01_load.t
+t/02_server_setup.t
+t/03_server_create_object.t
+t/04_server_replicate_object.t
+t/05_server_lock_object.t
+t/06_server_unlock_object.t
+t/07_server_find_instances.t
+t/08_server_delete_instance.t
+t/09_server_stat_object.t
+t/10_client_new.t
+t/11_client_create.t
+t/12_client_replicate.t
+t/13_client_cull.t
+t/14_client_lock.t
+t/15_client_unlock.t
+t/16_client_find_instances.t
+t/TEST.PL
+t/Test/IData.pm
+t/apache_03_server_create_object.x
+t/apache_04_server_replicate_object.x
+t/apache_05_server_lock_object.x
+t/conf/extra.conf.in
Index: /branches/jh_branches/jhoblitt/Nebulous/Makefile.PL
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/Makefile.PL	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/Makefile.PL	(revision 22254)
@@ -0,0 +1,27 @@
+unless (eval "use Module::Build::Compat 0.02; 1" ) {
+  print "This module requires Module::Build to install itself.\n";
+
+  require ExtUtils::MakeMaker;
+  my $yn = ExtUtils::MakeMaker::prompt
+    ('  Install Module::Build from CPAN?', 'y');
+
+  if ($yn =~ /^y/i) {
+    require Cwd;
+    require File::Spec;
+    require CPAN;
+
+    # Save this 'cause CPAN will chdir all over the place.
+    my $cwd = Cwd::cwd();
+    my $makefile = File::Spec->rel2abs($0);
+
+    CPAN::Shell->install('Module::Build::Compat');
+
+    chdir $cwd or die "Cannot chdir() back to $cwd: $!";
+    exec $^X, $makefile, @ARGV;  # Redo now that we have Module::Build
+  } else {
+    warn " *** Cannot install without Module::Build.  Exiting ...\n";
+    exit 1;
+  }
+}
+Module::Build::Compat->run_build_pl(args => \@ARGV);
+Module::Build::Compat->write_makefile();
Index: /branches/jh_branches/jhoblitt/Nebulous/README
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/README	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/README	(revision 22254)
@@ -0,0 +1,16 @@
+pod2text Poi::PixelData.pm > README
+
+If this is still here it means the programmer was too lazy to create the readme file.
+
+You can create it now by using the command shown above from this directory.
+
+At the very least you should be able to use this set of instructions
+to install the module...
+
+perl Build.PL
+./Build
+./Build test
+./Build install
+
+
+If you are on a windows box you should use 'nmake' rather than 'make'.
Index: /branches/jh_branches/jhoblitt/Nebulous/TODO
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/TODO	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/TODO	(revision 22254)
@@ -0,0 +1,17 @@
+cache some database tables (updated at some interval) 
+reuse/cache database statement handles
+code tiding/consistency
+volume handling for instance placement
+file creation retrying
+server config file
+make Apache::DBI behave
+WSDL
+split SOAP out of Client.pm
+error string support
+
+distant future
+--
+volume size/% in-use discovery
+server side per client lock tracking
+client side lookup table of filehandles/locks
+Perl only?, scoped lock release/filehandle destruction
Index: /branches/jh_branches/jhoblitt/Nebulous/Todo
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/Todo	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/Todo	(revision 22254)
@@ -0,0 +1,5 @@
+TODO list for Perl module Poi::PixelData
+
+- Nothing yet
+
+
Index: /branches/jh_branches/jhoblitt/Nebulous/bootstrap-moa-mp1.sh
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/bootstrap-moa-mp1.sh	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/bootstrap-moa-mp1.sh	(revision 22254)
@@ -0,0 +1,1 @@
+perl Build.PL -http_conf /usr/local/apache/conf/httpd.conf -apxs /usr/local/apache/bin/apxs -httpd /usr/local/apache/bin/httpd 
Index: /branches/jh_branches/jhoblitt/Nebulous/docs/apache_setup.txt
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/docs/apache_setup.txt	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/docs/apache_setup.txt	(revision 22254)
@@ -0,0 +1,37 @@
+- add the "idata" group to /etc/group
+- configure Apache to run as the idata group. 
+
+#
+# CGI interface
+#
+
+ScriptAlias /idata /usr/local/apache/perl/imageserver.pl
+
+<Directory "/usr/local/apache/perl">
+        AllowOverride None
+        Options None
+        Order allow,deny
+        Allow from all
+</Directory>
+
+PerlModule Apache::PerlRun
+<Location /perl>
+        SetHandler perl-script
+        PerlHandler Apache::PerlRun
+        Options ExecCGI
+        allow from all
+        PerlSendHeader On
+</Location>
+
+#
+# PS::IPP::IData::Apache interface
+#
+
+PerlModule Apache::DBI
+PerlModule PS::IPP::IData::Apache
+
+<Location /idata>
+        SetHandler perl-script
+        PerlHandler PS::IPP::IData::Apache
+</Location>
+
Index: /branches/jh_branches/jhoblitt/Nebulous/docs/c_api.h
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/docs/c_api.h	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/docs/c_api.h	(revision 22254)
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2004 Joshua Hoblitt
+ *
+ * $Id: c_api.h,v 1.1.1.1 2004-12-22 02:16:23 jhoblitt Exp $
+ */
+
+// should there be an argument for "suggested" storage location?
+int add( const char *ext_id, const char *data, int size )
+
+int delete( const char *ext_id )
+
+// should there be an argument for "suggested" storage location?
+int update( const char *ext_id, const char *data, int size )
+
+// should there be an argument for "suggested" storage location?
+int move( const char *old_ext_id, const char *new_ext_id )
+
+// open returns a fd 
+int open( const char *ext_id )
+
+int close( const char *ext_id )
+
+// maybe open and close are a bad idea, perhaps...
+int read( const char *ext_id, char *data )
Index: /branches/jh_branches/jhoblitt/Nebulous/docs/calc_gid.c
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/docs/calc_gid.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/docs/calc_gid.c	(revision 22254)
@@ -0,0 +1,5 @@
+#include <stdio.h>
+
+main () {
+    printf( "%d\n", 'i'+'m'+'a'+'g'+'e'+'d'+'a'+'t'+'a' + 's'+'e'+'r'+'v'+'e'+'r' );
+}
Index: /branches/jh_branches/jhoblitt/Nebulous/docs/database_setup.txt
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/docs/database_setup.txt	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/docs/database_setup.txt	(revision 22254)
@@ -0,0 +1,147 @@
+# Copyright (C) 2004  Joshua Hoblitt
+#
+# $Id: database_setup.txt,v 1.1.1.1 2004-12-22 02:16:23 jhoblitt Exp $
+
+### Database Setup
+
+DROP DATABASE IF EXISTS pixel;
+CREATE DATABASE pixel;
+INSERT INTO db VALUES ('%','pixel','pixel','y','y','y','y','y','y','y','y','y','y','y','y');
+INSERT INTO user
+(Host,User,Password,Select_priv,Insert_priv,Update_priv,Delete_priv,Create_priv,Drop_priv,Index_priv,Alter_priv,Show_db_priv)
+VALUES ('%','pixel',password('pixel'),'y','y','y','y','y','y','y','y','y');
+INSERT INTO user
+(Host,User,Password,Select_priv,Insert_priv,Update_priv,Delete_priv,Create_priv,Drop_priv,Index_priv,Alter_priv,Show_db_priv)
+VALUES ('localhost','pixel',password('pixel'),'y','y','y','y','y','y','y','y','y');
+FLUSH PRIVILEGES;
+
+USE pixel;
+
+### MySQL 4.0
+
+DROP TABLE IF EXISTS storage_object;
+CREATE TABLE storage_object (
+    so_id BIGINT NOT NULL AUTO_INCREMENT,
+    ext_id VARCHAR(255) BINARY NOT NULL UNIQUE,
+    class_id TINYINT NOT NULL,
+    comment VARCHAR(255),
+    read_lock TINYINT DEFAULT 0 NOT NULL,
+    write_lock ENUM( 'write' ),
+    epoch TIMESTAMP NOT NULL,
+    PRIMARY KEY(so_id),
+    KEY(ext_id(64)),
+    KEY(class_id)
+) ENGINE=innodb;
+
+DROP TABLE IF EXISTS instance;
+CREATE TABLE instance ( 
+    ins_id BIGINT NOT NULL AUTO_INCREMENT,
+    so_id BIGINT NOT NULL,
+    uri VARCHAR(255) BINARY NOT NULL UNIQUE,
+    sha1sum CHAR(40),
+    assigned_location BOOL,
+    epoch TIMESTAMP NOT NULL,
+    atime TIMESTAMP NOT NULL,
+    PRIMARY KEY(ins_id),
+    KEY(so_id),
+    KEY(uri(64))
+) ENGINE=innodb;
+
+DROP TABLE IF EXISTS lock_record;
+CREATE TABLE lock_record (
+        so_id BIGINT NOT NULL,
+        type ENUM( 'read', 'write' ) NOT NULL,
+        epoch TIMESTAMP,
+        KEY(so_ID)
+    ) ENGINE=innodb;
+
+DROP TABLE IF EXISTS volume;
+CREATE TABLE volume (
+        vol_id INT NOT NULL AUTO_INCREMENT,
+        name VARCHAR(255) UNIQUE BINARY NOT NULL,
+        uri VARCHAR(255) BINARY NOT NULL,
+        key(vol_id)
+    ) ENGINE=innodb;
+
+DROP TABLE IF EXISTS class;
+CREATE TABLE class (
+        class_id TINYINT NOT NULL AUTO_INCREMENT,
+        instance TINYINT NOT NULL,
+        priority TINYINT,
+        comment VARCHAR(255),
+        key(class_id)
+    ) ENGINE=innodb;
+
+### MySQL 4.1
+
+DROP TABLE IF EXISTS storage_object;
+CREATE TABLE storage_object (
+        so_id BIGINT NOT NULL AUTO_INCREMENT,
+        ext_id VARCHAR(255) UNIQUE BINARY NOT NULL,
+        comment VARCHAR(255),
+        epoch TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+        PRIMARY KEY(so_id),
+        KEY(ext_id(20))
+    ) ENGINE=innodb;
+
+
+DROP TABLE IF EXISTS instance;
+CREATE TABLE instance ( 
+        ins_id BIGINT NOT NULL AUTO_INCREMENT,
+        so_id BIGINT NOT NULL,
+        uri VARCHAR(255) BINARY NOT NULL,
+        sha1sum CHAR(40) ASCII,
+        read_lock TINYINT NOT NULL,
+        write_lock ENUM( 'write', 'wait' ),
+        atime TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+        PRIMARY KEY(ins_id),
+        KEY(so_id)
+    ) ENGINE=innodb;
+
+DROP TABLE IF EXISTS lock_record;
+CREATE TABLE lock_record (
+        ins_id BIGINT NOT NULL,
+        type ENUM( 'read', 'write', 'wait' ) NOT NULL,
+        epoch TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+    ) ENGINE=innodb;
+
+        epoch TIMESTAMP,
+
+### Initialization
+
+# NFS
+
+INSERT INTO volume VALUES (1,'po01','file:/tmp/po01');
+INSERT INTO volume VALUES (2,'po02','file:/tmp/po02');
+INSERT INTO volume VALUES (3,'po03','file:/tmp/po03');
+INSERT INTO volume VALUES (4,'po04','file:/tmp/po04');
+INSERT INTO volume VALUES (5,'po05','file:/tmp/po05');
+INSERT INTO volume VALUES (6,'po06','file:/tmp/po06');
+INSERT INTO volume VALUES (7,'po07','file:/tmp/po07');
+INSERT INTO volume VALUES (8,'po08','file:/tmp/po08');
+
+INSERT INTO class VALUES (1,3,0,'raw image data');
+
+### Example queries
+
+LOAD DATA INFILE '/home/moa/jhoblitt/tmp/storage_objects.txt' INTO TABLE storage_object; 
+LOAD DATA INFILE '/home/moa/jhoblitt/tmp/instances.txt' INTO TABLE instance; 
+
+SELECT * FROM storage_object LEFT JOIN instance ON storage_object.so_id=instance.so_id WHERE storage_object.ext_id = "foobarbaz1.fits";
+SELECT * FROM storage_object LEFT JOIN instance USING (so_id) WHERE storage_object.ext_id = "foobarbaz1.fits";
+
+SET AUTOCOMMIT = 0;
+BEGIN;
+UPDATE instance SET read_lock=read_lock+1 WHERE ins_id=1;
+INSERT INTO lock_record values (1, 'read', NULL);
+COMMIT;
+
+SET AUTOCOMMIT = 0;
+BEGIN;
+INSERT INTO storage_object values (NULL, 'myfooedbar.fits', 'mr. wizard made this', NULL);
+INSERT INTO instance values (NULL, LAST_INSERT_ID(), 'file://tmp/bat/man/12345a', 'da39a3ee5e6b4b0d3255bfef95601890afd80709',0, NULL, NULL);
+COMMIT;
+
+IF select write_lock from instance WHERE id=1 THEN
+ELSE
+ENDIF
Index: /branches/jh_branches/jhoblitt/Nebulous/docs/design.txt
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/docs/design.txt	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/docs/design.txt	(revision 22254)
@@ -0,0 +1,163 @@
+# Copryight (C) 2004  Joshua Hoblitt
+#
+# $Id: design.txt,v 1.1.1.1 2004-12-22 02:16:23 jhoblitt Exp $
+
+Terminology
+--
+    - Storage Object
+There is one Storage Object per resource (a file) that is being managed by the
+IPP Pixel Data Server.  A Storage Object maps an External Identifier (eg.
+file name) to a Storage ID that is internal to the IPP Pixel Data Server.  In
+addition, creation time and comments may be stored in a Storage Object.
+Storage Objects /do not/ contain the storage location of a resource as we need
+to allow for N copies of a resource to be stored.  The actual storage location
+of a resource is held in a Storage Object Instance, which maps to a Storage ID
+in a many-to-one relationship.
+
+    - [Storage Object] Instance
+A Storage Object Instance, or just Instance, represents a single copy of a
+resource being managed by the IPP Pixel Data Server. An Instance contains an
+Instance ID, the location of the resource as a URI, a checksum of the
+resource, a Storage ID, a read lock semaphore (unsigned integer), a write lock
+mutex (enum( 'write', 'wait', 'free' ) - is this better then using an int and
+constants?), and a UTC timestamp of the last modification date & time.
+
+    - Lock Record
+A Lock Record is used to double check the indicated lock state of an Instance
+and can be used to identify system failures.  It must contain the Instance ID
+of of the locked instance and the type of lock, eg, enum( 'read', 'write').
+
+Locking semantics
+--
+A lock is required to either read from or write to a Storage Object Instance
+being managed by the IPP Pixel Data Server.  Stated another way, locking
+granularity is managed on the level of Storage Object Instances instead of on
+the more abstract Storage Objects.  Although, from the client perspective,
+operations are carried out on a Storage Object.   The locking state of a
+Storage Object Instance is maintained by in the Storage Object Instance and a
+Lock Record.
+
+Two types of locks are used on an Instance, a semaphore is used to track read
+locks and an exclusive mutex is for write locking.
+
+    - Read lock
+In order to read from a Storage Object Instance the Read Lock semaphore must
+first be incremented by one and a Lock Record created.  After the read is
+completed the Lock Record must be deleted and the Read Lock semaphore
+decremented by one.  The incrementation of the Read Lock and Lock Record
+creation or the decrementation of the Read Lock and the Lock Record deletion
+must happen atomically.  A Read lock can not be aquired unless the Write mutex
+is in the state of 'free'.  If a Read lock can not be aquired, a client must
+not "spin lock" on the Instanace ID as it may be in the process of being
+removed.  Instead, the "spin lock" should be re-resolving the external
+identifier to a Storage ID, and the Storage ID to Storage Instances.
+
+    - Write lock
+In order to obtain a Write lock on a Storage Object Instance the Write Lock
+mutex must be in the state of 'free' and the Read lock semaphore must have a
+value of zero.  A client should either "spin lock" or fail if all the Lock
+mutex is set or the Read Semaphore has a non-zero value.  A Lock Record must
+be created atomically with the setting of the Write mutex.  The setting of the
+Write mutex and Lock Record insertion or the clearing of the Write mutex and
+the Lock Record deletion must happen atomically.  If the Write lock has a
+state of 'free' and the Read lock has a non-zero value, the Write lock may be
+set to the 'wait' state to prevent new Read locks from being aquired.
+
+Storage Object Operations
+--
+    - Add
+The resource must already be on disk (having been named through some sort of
+mechanism to generate new filenames).  A new Storage Object must be created with a new/unqiue  Storage ID and has
+an associated with an External Identifier.  At least one Instance must be
+created atomicaly at the same time.  Each new Instance must be initially created with a Write lock.
+The resource may then be written to it's storage location.  When the writing
+of the resource is completed the Write lock must be cleared as early as is
+feasible.  The association of an External Identifier must wait until Instance
+creation is completed
+
+    - Delete
+In order to remove a Storage Object, a Write lock must be obtained on
+/all/ associated Instances.  Then the resources are removed.  As each resource
+is deleted the corresponding Instance must be deleted.  After all Instances are
+removed the Storage Object must then be deleted.
+
+    - Update
+Storage Object Instances are not truly written to.  Instead, a new set of
+Storage Object Instances and a new Storage Object are created in parallel.
+
+Write locks must be aquired on all the orignal Intances of a Storage Object.
+Then an Add operation is performed to create a new Storage Object and
+associated Instances.  Then the External Identifier must be atomically moved
+from the original Storage Object to the new Storage Object.  After the
+External Identifier migration, the original Storage Object and it's Instances
+should be removed with a special case of the Delete operation (one that will
+operate on already held locks).  If the old Storage Object is merely
+abandoned, ie. it still exists but doesn't have an External Identifier, it and
+the associated Instances will be removed during the next Consistency Sweep.
+If a Delete is not peformed, the Write locks on the orignal Instances must be
+released. [use a dead Storage Object table or flag, similar to mark and sweep
+garbage collection?]
+
+    - Move
+This is the same operation as an Update except the new Instances are identical
+to the old.
+
+    - Open
+Sets a Read lock on an Instances of a Storage Object and determines it's
+storage location.  A Read lock must only be placed on one Instance of a
+Storage Object.  All Instances of a Storage Object should be iterated through
+in a "spin lock" until a Read lock is aquired.
+
+    - Close
+Releases a Read lock aquired with open.
+
+The lock must be released as soon as is feasible.
+
+Lock sweeping
+--
+In the event that a Storage Object operation fails to complete successfully
+stale locks will have to be identified and removed from the IPP Pixel Data
+Server Database.  This should be done periodically by comparing the entries
+in the Lock table to the list of active nodes maintained by the IPP
+Controller.  It should also happen as soon as possible after a node goes
+offline (triggered by the IPP Controller marking a node as offline?).  A sweep
+must be /completed/ before an offline node can be marked on-line.
+
+Once a node is determined to be offline all entries in the Lock table set by
+that node should be identified.   The locks on the Storage Object
+Instances pointed to by those entries should then be rolled back and Lock
+Record entries themselves must be removed from the lock table.
+
+Consistency sweeping
+--
+Periodically the IPP Pixel Data Server meta-data and Storage Object will need
+to be checked for sanity.  This would be similar to running fsck on a modern
+filesystem.  Consistency sweeping should include Lock sweeping and should be
+considered a superset.
+
+All Storage Objects must have at least once associated Instance.
+
+All Storage Object Instances must be associated with a Storage Object.
+
+All Storage Object Instances with a lock set must have the correct number of
+corresponding Lock Records.
+
+All Lock Records must be plausible, ie. the Instance pointed at must be in a
+corresponding state.
+
+Storage ID Allocation
+--
+Storage IDs are a signed 64-bit integer.  Negative values are considered
+reserved for future use.  Allocation should be incremental and, as we are
+unlikely to exceed 2^63 Storage Objects in the life time of the project, never
+reused.
+
+Allocated via [SQL auto-increment/stored procedure/app server?]
+
+Instance ID Allocation
+--
+Indentical to a Storage ID but exists in a unique namespace.
+
+Storage Location determination
+--
+???
Index: /branches/jh_branches/jhoblitt/Nebulous/docs/requirements.txt
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/docs/requirements.txt	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/docs/requirements.txt	(revision 22254)
@@ -0,0 +1,23 @@
+# Copyright (C) 2004  Joshua Hoblitt
+#
+# $Id: requirements.txt,v 1.1.1.1 2004-12-22 02:16:23 jhoblitt Exp $
+
+- store all image data aquired during the first two years of PS1's
+  operations.  Approximately 10^8 files (OTA level).
+
+- injest new data from the summit at the same rate it is produced when
+  operating at a cadence of 40s between integrations.  Approximately 400Mb/s.
+
+- support phase 1 & 2 processing. x files per t_min
+
+- support phase 3 & 4 processing. x files per t_min
+
+- support accessing small blocks of a file (ie., seek)
+
+what are the science drivers for these 3?
+--
+- data corruption is guarenteed to be detectable.
+
+- relibably store xx.x% of files 
+
+- xx.x% of stored images must be retreivable in xs.
Index: /branches/jh_branches/jhoblitt/Nebulous/docs/setup.txt
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/docs/setup.txt	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/docs/setup.txt	(revision 22254)
@@ -0,0 +1,33 @@
+
+- building and install the Perl modules (see: README)
+
+- create and initialize the database (see: docs/database_setup.txt)
+
+- setup the server
+
+As a CGI, work with either Apache 1.3.x or 2.0.x
+
+cp scripts/imagesever.pl /var/www/localhost/cgi-bin
+chmod 755 /var/www/localhost/cgi-bin/imageserver.pl
+touch /tmp/imageserver.log
+chmod 1777 /tmp/imageserver.log
+
+- test the imageserver.pl is working
+
+telnet localhost 80
+Trying 127.0.0.1...
+Connected to localhost.
+Escape character is '^]'.
+GET /cgi-bin/imageserver.pl HTTP/1.0
+
+HTTP/1.1 411 Length Required
+Date: Tue, 28 Sep 2004 21:15:58 GMT
+Server: Apache/2.0.49 (Gentoo/Linux)
+Content-Length: 0
+Connection: close
+Content-Type: text/plain; charset=ISO-8859-1
+
+Connection closed by foreign host.
+
+- if you get an HTTP 5xx code check your apache error_log
+
Index: /branches/jh_branches/jhoblitt/Nebulous/docs/tmp.txt
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/docs/tmp.txt	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/docs/tmp.txt	(revision 22254)
@@ -0,0 +1,44 @@
+client
+--
+new
+
+new_file
+                new_file
+                open_file
+
+find_file
+    find_instance
+
+open_file
+    find_file
+
+delete_file
+                delete_file
+    nuke
+
+copy_file
+    open_file
+    new_file
+
+move_file
+    copy_file
+    delete_file
+
+replicate_file
+    open_file
+                replicate_file
+
+cull_file
+    find_instance
+    delete_instance
+
+delete_instance
+    nuke
+                delete_instance
+
+find_instance
+                find_instance
+
+# missing
+file_stat
+lock ?
Index: /branches/jh_branches/jhoblitt/Nebulous/lib/Nebulous/Client.pm
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/lib/Nebulous/Client.pm	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/lib/Nebulous/Client.pm	(revision 22254)
@@ -0,0 +1,631 @@
+# Copyright (c) 2004  Joshua Hoblitt
+#
+# $Id: Client.pm,v 1.1.1.1 2004-12-22 02:16:23 jhoblitt Exp $
+
+package PS::IPP::IData::Client;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.01';
+
+use File::Copy qw();
+use Log::Log4perl qw(get_logger :levels);
+use PS::IPP::IData::Client::Log;
+use PS::IPP::IData::Util qw( :standard );
+use Params::Validate qw( validate validate_pos SCALAR );
+use SOAP::Lite +trace => [qw( debug )];
+use Time::HiRes qw( sleep );
+use URI;
+
+use constant LOCK_INTERVAL  => 1;
+use constant LOCK_DEFAULT   => 10;    # default to a 10s lock timeout
+
+my $log = get_logger( "PS::IPP::IData::Client" );
+
+$SOAP::Constants::PATCH_HTTP_KEEPALIVE = 1;
+
+# TODO remove most of the logdies
+
+sub import {
+    my $class = shift;
+
+    $log->debug( "entered - args: @_" );
+
+	my %args = validate( @_,
+        {
+            trace => {
+                type        => SCALAR,
+                default     => $ERROR,
+                callbacks   => {
+                    'is valid level' => sub {
+                        defined $LEVELS{ lc $_[0] };
+                    },
+                },
+            },
+        },
+    );
+
+    $log->level( $LEVELS{ lc $args{ 'trace' } } );
+
+    return 1;
+}
+
+sub new {
+    my $class = shift;
+
+    my %args = validate( @_,
+        {
+            proxy   => {
+                type        => SCALAR,
+            },
+            uri     => {
+                type        => SCALAR,
+                optional    => 1,
+            },
+        },
+    );
+
+    $log->debug( "entered - args: @{[ %args ]}" );
+
+    my $lite = SOAP::Lite->new(
+            proxy => $args{ 'proxy' },
+            uri   => $args{ 'uri' } || "http://pan-starrs.ifa.hawaii.edu/PS/IPP/IData/Server",
+    );
+
+    $log->logdie( "can not create SOAP::Lite object" ) unless $lite;
+
+    my $self = bless( { server => $lite }, ref $class || $class );
+
+    $log->debug( "leaving" );
+
+    return $self;
+}
+
+sub create {
+    my $self = shift;
+
+    my ( $key, @params ) = validate_pos( @_,
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is + integer' => sub { $_[0] =~ /^\d+$/ },
+            },
+            default     => 0,
+        },
+        {
+            type        => SCALAR,
+            optional    => 1,
+        },
+        {
+            type        => SCALAR,
+            optional    => 1,
+        },
+    );
+
+    $log->debug( "entered - key: $key, params: @params" );
+
+    # how should already existing files be handled?
+
+    my $response = $self->{ 'server' }->create_object( $key, @params );
+    if ( $response->fault ) {
+        $log->error( $response->faultcode, " - ", $response->faultstring );
+        $log->debug( "leaving" );
+
+        return undef;
+    }
+
+    my $uri = $response->result;
+
+    $log->debug( "server allocated $uri" );
+
+#    unless( $self->lock( $key, "write"  )) {
+#        $log->debug( "can not lock object" );
+#        $log->debug( "leaving" );
+#
+#        return undef;
+#    }
+
+    my $fh;
+    eval {
+        $fh = _open_uri( $uri, '+<' );
+    };
+    $log->logdie( $@ ) if $@;
+
+    $log->debug( "leaving" );
+
+    return $fh;
+}
+
+sub replicate {
+    my $self = shift;
+
+    my ( $key, @params ) = validate_pos( @_,
+        {
+            type        => SCALAR,
+        },
+        {
+            # volume
+            type        => SCALAR,
+            optional    => 1,
+        },
+    );
+
+    $log->debug( "entered - key: $key, params: @params" );
+
+    my $response = $self->{ 'server' }->replicate_object( $key, @params );
+    if ( $response->fault ) {
+        $log->error( $response->faultcode, " - ", $response->faultstring );
+        $log->debug( "leaving" );
+
+        return undef;
+    }
+
+    # don't let anybody start reading the new instance before it's fully
+    # written
+    my $fh  = $self->open( $key, 'write' );
+    unless ( $fh ) {
+        $log->debug( "can't open $key" );
+        $log->debug( "leaving" );
+
+        return undef;
+    }
+
+    $log->debug( "server allocated new instance" );
+
+    my $uri = $response->result;
+
+    my $new_fh;
+    eval {
+        $new_fh = _open_uri( $uri, '>' );
+    };
+    $log->logdie( $@ ) if $@;
+
+    File::Copy::copy( $fh, $new_fh ) or $log->logdie( "can not copy instance $uri" );
+
+    $log->debug( "copied instance" );
+
+    close( $fh ) or $log->logdie( "can not close $key" );
+    close( $new_fh ) or $log->logdie( "can not close $key" );
+
+#    unless ($self->unlock( $key, "write" )) {
+#        $log->debug( "can not lock object" );
+#        $log->debug( "leaving" );
+#
+#        return undef;
+#    }
+
+    $log->debug( "leaving" );
+
+    return $uri;
+}
+
+sub cull {
+    my $self = shift;
+
+    my ( $key ) = validate_pos( @_,
+        {
+            type => SCALAR,
+        },
+    );
+
+    $log->debug( "entered - key: $key" );
+
+    # need some way to determine which is the best instance to remove
+    my $locations = $self->find_instances( $key );
+    unless ( $locations ) {
+            $log->debug( "no instances" );
+            $log->debug( "leaving" );
+
+            return undef;
+    }
+
+    if ( scalar @{ $locations } < 2 ) {
+        $log->debug( "can not cull - not enough instances" );
+        $log->debug( "leaving" );
+
+        return undef;
+    }
+
+    # make sure nobody is reading while we are deleting
+    unless ($self->lock( $key, "write" )) {
+        $log->debug( "can not lock object" );
+        $log->debug( "leaving" );
+
+        return undef;
+    }
+
+    my $status = $self->delete_instance( $locations->[0] );
+
+    unless ($self->unlock( $key, "write" )) {
+        $log->debug( "can not lock object" );
+        $log->debug( "leaving" );
+
+        return undef;
+    }
+
+    $log->debug( "leaving" );
+
+    return $status;
+}
+
+sub lock {
+    my $self = shift;
+
+    my ( $key, $type, $timeout ) = validate_pos( @_,
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is read or write' => sub { $_[0] =~ /^(?:read|write)$/ },
+            },
+        },
+        {
+            type        => SCALAR,
+            default     => LOCK_DEFAULT,
+            optional    => 1,
+            callbacks   => {
+                'is + integer' => sub { $_[0] =~ /^\d+$/ },
+            },
+        },
+    );
+
+    my $endtime = time() + $timeout;
+
+    $log->debug( "entered - key: $key, type: $type, timeout: $timeout" );
+
+    my $locked;
+
+    while ( time() < $endtime ) {
+        $log->debug( "trying to get a $type lock..." );
+
+        my $response = $self->{ 'server' }->lock_object( $key, $type );
+        if ( $response->fault ) {
+            $log->error( $response->faultcode, " - ", $response->faultstring );
+            $log->debug( "leaving" );
+            
+            # don't bother to spin lock if the object doesn't exist
+            if ( $response->faultstring =~ /storage object does not exist/ ) {
+                return undef;
+            }
+
+            next;
+        }
+
+        $locked = $response->result;
+        if ( $locked ) {
+            $log->debug( "got a $type lock" );
+            last;
+        }
+    } continue {
+        $log->debug( "sleeping..." );
+        sleep LOCK_INTERVAL;
+    }
+
+    $log->debug( "can not get a lock" ) unless $locked;
+
+    $log->debug( "leaving" );
+    
+    return $locked ? 1 : undef;
+}
+
+sub unlock {
+    my $self = shift;
+
+    my ( $key, $type ) = validate_pos( @_,
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is read or write' => sub { $_[0] =~ /^(?:read|write)$/ },
+            },
+        },
+    );
+
+    $log->debug( "entered - key: $key, type: $type" );
+
+    my $unlocked;
+
+    $log->debug( "trying to release a $type lock..." );
+
+    my $response = $self->{ 'server' }->unlock_object( $key, $type );
+    if ( $response->fault ) {
+        $log->error( $response->faultcode, " - ", $response->faultstring );
+        $log->debug( "leaving" );
+
+        return undef;
+    }
+
+    $unlocked = $response->result;
+    if ( $unlocked ) {
+        $log->debug( "released a $type lock" );
+    } else {
+        $log->debug( "can not release $type lock" );
+    }
+
+    $log->debug( "leaving" );
+    
+    return $unlocked ? 1 : undef;
+}
+
+sub find_instances {
+    my $self = shift;
+
+    my ( $key, @params ) = validate_pos( @_,
+        {
+            type        => SCALAR,
+        },
+        {
+            #volume
+            type        => SCALAR,
+            optional    => 1,
+        },
+    );
+
+    $log->debug( "entered - key: $key" );
+
+    my $response = $self->{ 'server' }->find_instances( $key, @params );
+    if ( $response->fault ) {
+        $log->error( $response->faultcode, " - ", $response->faultstring );
+        $log->debug( "leaving" );
+
+        return undef;
+    }
+
+    my $uris = $response->result;
+
+    $log->debug( "server found: @$uris" );
+
+#    foreach my $path ( @{ $uris } ) {
+#        $path = _get_file_path( $path );
+#    }
+
+    $log->debug( "leaving" );
+
+    return $uris;
+}
+
+sub find {
+    my $self = shift;
+
+    my ( $key ) = validate_pos( @_,
+        {
+            type        => SCALAR,
+        },
+    );
+
+    $log->debug( "entered - key: $key" );
+
+    my $locations = $self->find_instances( $key );
+
+    my $path;
+    eval {
+        $path = _get_file_path( $locations->[0] );
+    };
+    $log->logdie( $@ ) if $@;
+
+    $log->debug( "leaving" );
+
+    return $path;
+}
+
+sub open {
+    my $self = shift;
+
+    my ( $key, $type ) = validate_pos( @_,
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is read or write' => sub { $_[0] =~ /^(?:read|write)$/ },
+            },
+        },
+    );
+
+    $log->debug( "entered - key: $key, type: $type" );
+
+    my $fh;
+
+    # need to figure out which is the most optimal copy to open
+    my $locations = $self->find_instances( $key );
+
+    my $path;
+    eval {
+        $path = _get_file_path( $locations->[0] );
+    };
+    $log->logdie( $@ ) if $@;
+
+    unless ( $path ) {
+        $log->debug( "no instances" );
+
+        if ( $type eq 'write' ) {
+            $log->debug( "creating a new storage object" );
+
+            my $ret = $self->create( $key );
+
+            $log->debug( "leaving" );
+
+            return $ret;
+        } else {
+            $log->debug( "leaving" );
+            return undef;
+        }
+    }
+
+    if ( $type eq 'write' ) {
+#        unless ($self->lock( $key, $type )) {
+#            $log->debug( "can not lock object" );
+#            $log->debug( "leaving" );
+#
+#            return undef;
+#        }
+
+        eval {
+            $fh = _get_filehandle( $path, '+<' );
+        };
+        $log->logdie( $@ ) if $@;
+    } elsif ( $type eq 'read' ) {
+#            unless ($self->lock( $key, $type )) {
+#            $log->debug( "can not lock object" );
+#            $log->debug( "leaving" );
+#
+#            return undef;
+#        }
+
+        eval {
+            $fh = _get_filehandle( $path, '<' );
+        };
+        $log->logdie( $@ ) if $@;
+    }
+
+    $log->debug( "leaving" );
+
+    return $fh;
+}
+
+sub delete {
+    my $self = shift;
+
+    my ( $key ) = validate_pos( @_,
+        {
+            type => SCALAR,
+        },
+    );
+
+    $log->debug( "entered - key: $key" );
+
+    unless ($self->lock( $key, "write" )) {
+        $log->debug( "can not lock object" );
+        $log->debug( "leaving" );
+
+        return undef;
+    }
+
+    my $locations = $self->find_instances( $key );
+        
+    foreach my $uri ( @$locations ) {
+        $self->delete_instance( $uri );
+    }
+
+    # lock is implicitly removed when the last storage object is deleted
+
+    $log->debug( "leaving" );
+
+    return 1;
+}
+
+sub copy {
+    my $self = shift;
+
+    my ( $key, $new_key ) = validate_pos( @_,
+        {
+            type => SCALAR,
+        },
+        {
+            type => SCALAR,
+        },
+    );
+
+    $log->debug( "entered - key: $key, new_key: $new_key" );
+
+    my $fh      = $self->open( $key, 'read' );
+    my $new_fh  = $self->create( $new_key );
+
+    File::Copy::copy( $fh, $new_fh ) or $log->logdie( "can not copy object $key" );
+
+    close( $fh ) or $log->logdie->( "can not close $key" );
+    close( $new_fh ) or $log->logdie->( "can not close $new_key" );
+
+    unless ($self->unlock( $key, "read" )) {
+        $log->debug( "can not lock object" );
+        $log->debug( "leaving" );
+
+        return undef;
+    }
+
+    unless ($self->unlock( $new_key, "write" )) {
+        $log->debug( "can not lock object" );
+        $log->debug( "leaving" );
+
+        return undef;
+    }
+
+    $log->debug( "leaving" );
+
+    return 1;
+}
+
+sub move {
+    my $self = shift;
+
+    my ( $key, $new_key ) = validate_pos( @_,
+        {
+            type => SCALAR,
+        },
+        {
+            type => SCALAR,
+        },
+    );
+
+    $log->debug( "entered - key: $key, new_key: $new_key" );
+
+    $self->copy( $key, $new_key );
+    $self->delete( $key );
+
+    $log->debug( "leaving" );
+
+    return 1;
+}
+
+sub delete_instance {
+    my $self = shift;
+
+    my ( $uri ) = validate_pos( @_,
+        {
+            type => SCALAR,
+        },
+    );
+
+    $log->debug( "entered - uri: $uri" );
+
+    # it is being assumed here that it is better to have files on disk and not in
+    # the database then the inverse.
+
+    my $path;
+    eval {
+        $path = _get_file_path( $uri );
+    };
+    $log->logdie( $@ ) if $@;
+
+    my $response = $self->{ 'server' }->delete_instance( $uri );
+    if ( $response->fault ) {
+        $log->error( $response->faultcode, " - ", $response->faultstring );
+        $log->debug( "leaving" );
+
+        return undef;
+    }
+
+    $log->debug( "server deleted instance" );
+
+    eval {
+        _nuke_file( $path );
+    };
+    $log->logdie( $@ ) if $@;
+
+    $log->debug( "leaving" );
+
+    return $uri;
+}
+
+1;
+
+__END__
Index: /branches/jh_branches/jhoblitt/Nebulous/lib/Nebulous/Client/HTTP.pm
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/lib/Nebulous/Client/HTTP.pm	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/lib/Nebulous/Client/HTTP.pm	(revision 22254)
@@ -0,0 +1,26 @@
+# Copyright (c) 2004  Joshua Hoblitt
+#
+# $Id: HTTP.pm,v 1.1.1.1 2004-12-22 02:16:24 jhoblitt Exp $
+
+package PS::IPP::IData::Client::HTTP;
+
+use strict;
+
+our $VERSION = '0.01';
+
+use base qw( SOAP::Lite );
+
+sub call {
+    my $result = $_[0]->SUPER::call( @_ );
+
+    if (( UNIVERSAL::isa( $result, 'SOAP::SOM' )) && $result->fault ) {
+
+        return $result->faultstring;
+    }
+
+    return $result;
+}
+
+1;
+
+__END__
Index: /branches/jh_branches/jhoblitt/Nebulous/lib/Nebulous/Client/Log.pm
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/lib/Nebulous/Client/Log.pm	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/lib/Nebulous/Client/Log.pm	(revision 22254)
@@ -0,0 +1,33 @@
+# Copyright (c) 2004  Joshua Hoblitt
+#
+# $Id: Log.pm,v 1.1.1.1 2004-12-22 02:16:24 jhoblitt Exp $
+
+package PS::IPP::IData::Client::Log;
+
+use strict;
+
+our $VERSION = '0.01';
+
+use Log::Log4perl;
+
+my $conf = q(
+    log4perl.category.PS.IPP.IData.Client = DEBUG, Screen
+#log4perl.category.PS.IPP.IData.Client = DEBUG, CLIENTLOGFILE, Screen
+
+    log4perl.appender.Screen        = Log::Log4perl::Appender::Screen
+    log4perl.appender.Screen.stderr = 0
+    log4perl.appender.Screen.layout = Log::Log4perl::Layout::PatternLayout
+    log4perl.appender.Screen.layout.ConversionPattern = %d | %H | %p | %M - %m%n
+
+    log4perl.appender.CLIENTLOGFILE           = Log::Log4perl::Appender::File
+    log4perl.appender.CLIENTLOGFILE.filename  = /tmp/idata_client.log
+    log4perl.appender.CLIENTLOGFILE.mode      = append
+    log4perl.appender.CLIENTLOGFILE.layout    = Log::Log4perl::Layout::PatternLayout
+    log4perl.appender.CLIENTLOGFILE.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} | %H | %p | %M - %m%n
+);
+
+Log::Log4perl::init( \$conf );
+
+1;
+
+__END__
Index: /branches/jh_branches/jhoblitt/Nebulous/lib/Nebulous/Server.pm
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/lib/Nebulous/Server.pm	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/lib/Nebulous/Server.pm	(revision 22254)
@@ -0,0 +1,660 @@
+# Copyright (c) 2004  Joshua Hoblitt
+#
+# $Id: Server.pm,v 1.1.1.1 2004-12-22 02:16:24 jhoblitt Exp $
+
+package PS::IPP::IData::Server;
+
+use strict;
+use warnings;
+no warnings qw( uninitialized );
+
+our $VERSION = '0.01';
+
+use DBI;
+use PS::IPP::IData::Util qw( :standard );
+use PS::IPP::IData::Server::Log;
+use PS::IPP::IData::Server::SQL;
+use Log::Log4perl;
+use Params::Validate qw( validate_pos SCALAR );
+
+my %class = (
+    raw => 1,
+);
+
+my $log;
+my $sql;
+my $db;
+
+sub setup {
+    my $self = shift;
+
+    my ( $dsn, $user, $passwd ) = validate_pos( @_,
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR,
+        },
+    );
+
+    $log = Log::Log4perl::get_logger( "PS::IPP::IData::Server" );
+    $sql = PS::IPP::IData::Server::SQL->new;
+
+    $log->debug( "entered - @_" );
+
+    eval {
+        $db = DBI->connect(
+            $dsn,
+            $user,
+            $passwd,
+            {
+                RaiseError => 1,
+                PrintError => 0,
+                AutoCommit => 0,
+            },
+        );
+
+        $db->do( $sql->set_transaction_model );
+
+        $db->commit;
+    };
+    if ( $@ ) {
+        $db->rollback if $db;
+        $log->logdie( "database error: $@" );
+    }
+
+    $log->debug( "connected to database: ", sub { $db->data_sources; } );
+
+    $log->debug( "leaving" );
+
+    return 1;
+}
+
+sub create_object {
+    my $self = shift;
+
+    $log->debug( "entered - @_" );
+
+    my ( $key, $class, $volume, $comment ) = validate_pos( @_,
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is + integer' => sub { $_[0] =~ /^\d+$/ },
+            },
+            default     => 0,
+        },
+        {
+            type        => SCALAR,
+            optional    => 1,
+        },
+        {
+            type        => SCALAR,
+            optional    => 1,
+        },
+    );
+
+    # TODO do some intelligent volume allocated 
+    # add hooks to make this decision based on avaiable space
+    $volume = _get_storage_volume( $volume );
+
+    # check that the comment isn't too long
+    # check that the class requested is valid
+    # check that the volume requested is valid
+
+    my $uri;
+    my $ins_id;
+
+    eval {
+        {
+            # create storage_object
+            my $query = $db->prepare( $sql->new_object ); 
+            $query->execute( $key, $class, $comment );
+        }
+
+        {
+            # create instance with no URI
+            my $query = $db->prepare( $sql->new_object_instance );
+            $query->execute;
+        }
+
+        {
+            # get instance ID
+            my $query = $db->prepare( $sql->last_insert_id );
+            $query->execute;
+            ( $ins_id ) = $query->fetchrow_array;
+        }
+    };
+    if ( $@ ) {
+        $db->rollback;
+        $log->logdie( "database error: $@" );
+    }
+
+    $uri = "$volume/$key.$ins_id";
+
+    $log->debug( "generated uri $uri");
+
+    my $path = _get_file_path( $uri );
+
+    # TODO add some stuff here to retry if unsucessful
+    eval {
+        _create_empty_file( $path );
+    };
+    if ( $@ ) {
+        $db->rollback;
+        $log->logdie( $@ );
+    }
+
+    eval {
+        {
+            my $query = $db->prepare( $sql->update_instance_uri );
+            $query->execute( $uri, $ins_id );
+        }
+
+        $db->commit;
+    };
+    $log->logdie( "database error: $@" ) if $@;
+
+    $log->debug( "leaving" );
+
+    return $uri;
+}
+
+sub replicate_object {
+    my $self = shift;
+
+    $log->debug( "entered - @_" );
+
+    my ( $key, $volume ) = validate_pos( @_,
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR,
+            optional    => 1,
+        },
+    );
+
+    # TODO
+    $volume = _get_storage_volume();
+
+    my $path;
+    my $uri;
+
+    my $so_id;
+    my $ins_id;
+    eval {
+        {
+            my $query = $db->prepare( $sql->get_object_instances );
+            my $rows = $query->execute( $key );
+
+            unless ( $rows > 0 ) {
+                $db->rollback;
+                $log->logdie( "storage object does not exist" );
+            }
+
+            $so_id = $query->fetchrow_hashref->{ 'so_id' };
+        }
+
+        {
+            my $query = $db->prepare( $sql->new_instance );
+            $query->execute( $so_id );
+        }
+
+        {
+            my $query = $db->prepare( $sql->last_insert_id );
+            $query->execute();
+
+            ( $ins_id ) = $query->fetchrow_array;
+        }
+
+        {
+            $uri = "$volume/$key.$ins_id";
+            $log->debug( "generated uri $uri");
+
+            my $query = $db->prepare( $sql->update_instance_uri );
+            $query->execute( $uri, $ins_id );
+        }
+    };
+    if ( $@ ) {
+        $db->rollback;
+        $log->logdie( "database error: $@" );
+    }
+
+
+    eval {
+        $path = _get_file_path( $uri );
+        $path = _create_empty_file( $path );
+    };
+    if ( $@ ) {
+        $db->rollback;
+        $log->logdie( $@ );
+    }
+
+    $db->commit;
+
+    $log->debug( "leaving" );
+
+    return $uri;
+}
+
+sub lock_object {
+    my $self = shift;
+
+    $log->debug( "entered - @_" );
+
+    my ( $key, $type ) = validate_pos( @_,
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is read or write' => sub { $_[0] =~ /^(?:read|write)$/ },
+            },
+        },
+    );
+
+    my $so_id;
+    my $read_lock;
+    my $write_lock;
+
+    eval {
+        # this will set update locks
+        my $query = $db->prepare( $sql->get_object_locks );
+        my $rows = $query->execute( $key );
+        unless ( $rows == 1 ) {
+            $log->logdie( "storage object does not exist" );
+        }
+
+        my $row = $query->fetchrow_hashref;
+
+        $so_id      = $row->{ 'so_id' };
+        $read_lock  = $row->{ 'read_lock' };
+        $write_lock = $row->{ 'write_lock' };
+    };
+    if ( $@ ) {
+        $db->rollback;
+        $log->logdie( "database error: $@" );
+    }
+
+    if ( $type eq 'write' ) {
+        # can't set a write lock twice and
+        # can't set a write lock if there are read locks
+        if ( $write_lock ) {
+            eval { $db->rollback; };
+            $log->logdie( "database error: $@" ) if $@;
+
+            $log->logdie( "can not write lock twice" );
+        }
+            
+        if ( $read_lock > 0 ) {
+            eval { $db->rollback; };
+            $log->logdie( "database error: $@" ) if $@;
+
+            $log->logdie( "can not write lock after read lock" );
+        }
+
+        eval {
+            my $query = $db->prepare( $sql->set_write_lock );
+            my $rows = $query->execute( $key );
+        
+            # if we affected more then one row something very bad has happened.
+            unless ( $rows == 1 ) {
+                $log->logdie( "affected row count is $rows instead of 1" );
+            }
+
+            $db->commit;
+        };
+        if ( $@ ) {
+            $db->rollback;
+            $log->logdie( "database error: $@" );
+        }
+
+        $log->debug( "leaving" );
+
+        return 1;
+    }
+
+    if ( $type eq 'read' ) {
+        # can't set a read lock if there's a write lock
+        if ( $write_lock ) {
+            eval { $db->rollback; };
+            $log->logdie( "database error: $@" ) if $@;
+
+            $log->logdie( "can not read lock after write lock" );
+        }
+
+        eval {
+            my $query = $db->prepare( $sql->increment_read_lock );
+            my $rows = $query->execute( $key );
+        
+            # if we affected more then one row something very bad has happened.
+            unless ( $rows == 1 ) {
+                $log->logdie( "affected row count is $rows instead of 1" );
+            }
+
+            $db->commit;
+        };
+        if ( $@ ) {
+            $db->rollback;
+            $log->logdie( "database error: $@" );
+        }
+
+        $log->debug( "leaving" );
+
+        return 1;
+    }
+
+    # never reached
+    $log->logdie( "this should never happen" );
+}
+
+sub unlock_object {
+    my $self = shift;
+
+    $log->debug( "entered - @_" );
+
+    my ( $key, $type ) = validate_pos( @_,
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is read or write' => sub { $_[0] =~ /^(?:read|write)$/ },
+            },
+        },
+    );
+
+    my $so_id;
+    my $read_lock;
+    my $write_lock;
+
+    eval {
+        # this will set update locks
+        my $query = $db->prepare( $sql->get_object_locks );
+        my $rows = $query->execute( $key );
+        unless ( $rows == 1 ) {
+            $log->logdie( "storage object does not exist" );
+        }
+
+        my $row = $query->fetchrow_hashref;
+
+        $so_id      = $row->{ 'so_id' };
+        $read_lock  = $row->{ 'read_lock' };
+        $write_lock = $row->{ 'write_lock' };
+    };
+    if ( $@ ) {
+        $db->rollback;
+        $log->logdie( "database error: $@" );
+    }
+
+    if ( $type eq 'write' ) {
+        # can't remove a write lock if it doesn't exist
+        if ( $read_lock ) {
+            eval { $db->rollback; };
+            $log->logdie( "database error: $@" ) if $@;
+           
+            $log->logdie( "can not have a write lock under a read lock" );
+        }
+        unless ( $write_lock ) {
+            eval { $db->rollback; };
+            $log->logdie( "database error: $@" ) if $@;
+
+            $log->logdie( "can not remove non-existant write lock" );
+        }
+
+        eval {
+            my $query = $db->prepare( $sql->delete_write_lock );
+            my $rows = $query->execute( $key );
+        
+            # if we affected more then one row something very bad has happened.
+            unless ( $rows == 1 ) {
+                $log->logdie( "affected row count is $rows instead of 1" );
+            }
+
+            $db->commit;
+        };
+        if ( $@ ) {
+            $db->rollback;
+            $log->logdie( "database error: $@" );
+        }
+
+        $log->debug( "leaving" );
+
+        return 1;
+    }
+
+    if ( $type eq 'read' ) {
+        # can't remove a read lock if there's a write lock and
+        # can't remove a read lock if there aren't any
+        if ( $write_lock ) {
+            eval { $db->rollback; };
+            $log->logdie( "database error: $@" ) if $@;
+           
+            $log->logdie( "can not have a read lock under a write lock" );
+        }
+           
+        if ( $read_lock == 0 ) {
+            eval { $db->rollback; };
+            $log->logdie( "database error: $@" ) if $@;
+           
+            $log->logdie( "can not remove non-existant read lock" );
+        }
+
+        eval {
+            my $query = $db->prepare( $sql->decrement_read_lock );
+            my $rows = $query->execute( $key );
+        
+            # if we affected more then one row something very bad has happened.
+            unless ( $rows == 1 ) {
+
+                $log->logdie( "affected row count is $rows instead of 1" );
+            }
+
+            $db->commit;
+        };
+        if ( $@ ) {
+            $db->rollback;
+            $log->logdie( "database error: $@" );
+        }
+
+        $log->debug( "leaving" );
+
+        return 1;
+    }
+
+    # never reached
+    $log->logdie( "this should never happen" );
+}
+
+
+sub find_instances {
+    my $self = shift;
+
+    $log->debug( "entered - @_" );
+
+    my ( $key, $volume ) = validate_pos( @_,
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR,
+            optional    => 1,
+        },
+    );
+
+    # TODO restrict search to a volume
+
+    my $query;
+
+    eval {
+        $query = $db->prepare( $sql->get_object_instances );
+        $query->execute( $key );
+    };
+    $log->logdie( "database error: $@" ) if $@;
+
+    my @locations;
+
+    while ( my $row = $query->fetchrow_hashref ) {
+        my $instance = $row->{ 'uri' };
+        push @locations, $instance if $instance;
+    }
+
+    $log->logdie( "no instances found" ) unless ( scalar @locations );
+
+    $log->debug( "leaving" );
+
+    return \@locations;
+}
+
+sub delete_instance {
+    my $self = shift;
+
+    $log->debug( "entered - @_" );
+
+    my ( $uri ) = validate_pos( @_,
+        {
+            type => SCALAR,
+        },
+    );
+
+    eval {
+        my $so_id;
+        my $instances;
+        # get so_id
+        {
+            my $query = $db->prepare( $sql->get_object_from_uri );
+            my $rows = $query->execute( $uri );
+
+            unless ( $rows > 0 ) {
+                $log->logdie( "no instance is associated with uri" );
+            }
+
+            $so_id = $query->fetchrow_hashref->{ 'so_id' };
+        }
+
+        {
+            my $query = $db->prepare( $sql->get_instance_count );
+            $query->execute( $so_id );
+
+            $instances = $query->fetchrow_hashref->{ 'count(ins_id)' };
+        }
+
+        # remove instance
+        {
+            my $query = $db->prepare( $sql->delete_instance );
+            my $rows = $query->execute( $uri );
+            
+            # if we affected something other then one row something very bad has happened
+            unless ( $rows == 1 ) {
+                $log->logdie( "affected row count is $rows instead of 1" );
+            }
+        }
+
+        # if we just deleted the last instance associated with a storage object
+        # remove it too
+        if ( $instances == 1 ) {
+            # we just removed the last instance
+            my $query = $db->prepare( $sql->delete_object );
+            my $rows = $query->execute( $so_id );
+
+            # if we affected something other then one row something very bad has happened
+            unless ( $rows == 1 ) {
+                $log->logdie( "affected row count is $rows instead of 1" );
+            }
+        }
+
+        $db->commit;
+    };
+    if ( $@ ) {
+        $db->rollback;
+        $log->logdie( "database error: $@" );
+    }
+
+    $log->debug( "leaving" );
+
+    return 1;
+}
+
+sub stat_object {
+    my $self = shift;
+
+    $log->debug( "entered - @_" );
+
+    my ( $key ) = validate_pos( @_,
+        {
+            type => SCALAR,
+        },
+    );
+
+    my $stat;
+    eval {
+        my $query = $db->prepare( $sql->get_object );
+        my $rows = $query->execute( $key );
+
+        unless ( $rows == 1 ) {
+            $log->logdie( "no storage object found" );
+        }
+
+        $stat = $query->fetchrow_arrayref;
+
+        $db->commit;
+    };
+    if ( $@ ) {
+        $db->rollback;
+        $log->logdie( "database error: $@" );
+    }
+
+    $log->debug( "leaving" );
+
+    return $stat;
+}
+
+sub _get_storage_volume {
+    $log->debug( "entered - @_" );
+
+    my $name = shift;
+
+    my $volume;
+    {
+        my $query;
+        eval {
+            # TODO cache this?
+            my $rows;
+            if ( $name ) {
+                $query = $db->prepare( qq{ SELECT uri FROM volume WHERE name = ? ORDER BY rand() LIMIT 1 } );
+                $rows = $query->execute( $name );
+            } else {
+                $query = $db->prepare( qq{ SELECT uri FROM volume ORDER BY rand() LIMIT 1 } );
+                $rows = $query->execute;
+            }
+
+            # there has to be atleast one storage volume
+            unless ( $rows > 0 ) {
+                $db->rollback;
+
+                $log->logdie( "affected row count is $rows instead of 1" );
+            } else {
+                $db->commit;
+            }
+        };
+        $log->logdie( "database error: $@" ) if $@;
+
+        $volume = $query->fetchrow_hashref->{ 'uri' };
+    }
+
+    $log->debug( "leaving" );
+
+    return $volume;
+}
+
+1;
+
+__END__
Index: /branches/jh_branches/jhoblitt/Nebulous/lib/Nebulous/Server/Apache.pm
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/lib/Nebulous/Server/Apache.pm	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/lib/Nebulous/Server/Apache.pm	(revision 22254)
@@ -0,0 +1,21 @@
+# Copyright (C) 2004  Joshua Hoblitt
+#
+# $Id
+
+package PS::IPP::IData::Server::Apache;
+
+use strict;
+
+our $VERSION = 0.01;
+
+use SOAP::Transport::HTTP;
+
+my $server = SOAP::Transport::HTTP::Apache
+    ->dispatch_to( 'PS::IPP::IData::Server' )
+    ->options({ compress_threshold => 10 * 1024 });
+
+sub handler { $server->handler(@_); }
+
+1;
+
+__END__
Index: /branches/jh_branches/jhoblitt/Nebulous/lib/Nebulous/Server/Log.pm
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/lib/Nebulous/Server/Log.pm	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/lib/Nebulous/Server/Log.pm	(revision 22254)
@@ -0,0 +1,27 @@
+# Copyright (c) 2004  Joshua Hoblitt
+#
+# $Id: Log.pm,v 1.1.1.1 2004-12-22 02:16:24 jhoblitt Exp $
+
+package PS::IPP::IData::Server::Log;
+
+use strict;
+
+our $VERSION = '0.01';
+
+use Log::Log4perl;
+                                                                                                                                          
+my $conf = q(
+    log4perl.category.PS.IPP.IData.Server = DEBUG, SERVERLOGFILE
+
+    log4perl.appender.SERVERLOGFILE           = Log::Log4perl::Appender::File
+    log4perl.appender.SERVERLOGFILE.filename  = /tmp/idata_server.log
+    log4perl.appender.SERVERLOGFILE.mode      = append
+    log4perl.appender.SERVERLOGFILE.layout    = Log::Log4perl::Layout::PatternLayout
+    log4perl.appender.SERVERLOGFILE.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} | %H | %p | %M - %m%n
+);
+                                                                                                                          
+Log::Log4perl::init( \$conf );
+
+1;
+
+__END__
Index: /branches/jh_branches/jhoblitt/Nebulous/lib/Nebulous/Server/SQL.pm
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/lib/Nebulous/Server/SQL.pm	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/lib/Nebulous/Server/SQL.pm	(revision 22254)
@@ -0,0 +1,107 @@
+# Copyright (c) 2004  Joshua Hoblitt
+#
+# $Id: SQL.pm,v 1.1.1.1 2004-12-22 02:16:24 jhoblitt Exp $
+
+package PS::IPP::IData::Server::SQL;
+
+use strict;
+
+our $VERSION = '0.01';
+
+use base qw( Class::Accessor::Fast );
+
+my %sql = (
+    set_transaction_model   => qq{
+        SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED
+    },
+    update_instance_uri => qq{
+        UPDATE instance SET uri = ? WHERE ins_id = ?
+    },
+    last_insert_id      => qq{
+        SELECT LAST_INSERT_ID()
+    },
+    new_object          => qq{
+        INSERT INTO storage_object
+        VALUES (NULL, ?, ?, ?, 0, NULL, NULL)
+    },
+    delete_object       => qq{
+        DELETE FROM storage_object
+        WHERE so_id = ?
+    },
+    new_object_instance  => qq{
+        INSERT INTO instance 
+        VALUES (NULL, LAST_INSERT_ID(), 'error', NULL, NULL, NULL, NULL)
+    },
+    new_instance        => qq{
+        INSERT INTO instance
+        VALUES (NULL, ?, 'error', NULL,NULL, NULL, NULL)
+    },
+    get_object          => qq{
+        SELECT *
+        FROM storage_object
+        WHERE ext_id = ?
+    },
+    get_object_locks    => qq{
+        SELECT so_id, read_lock, write_lock 
+        FROM storage_object
+        WHERE ext_id = ?
+        FOR UPDATE
+    },
+    set_write_lock      => qq{
+        UPDATE storage_object
+        SET write_lock = 'write'
+        WHERE ext_id = ?
+    },
+    delete_write_lock   => qq{
+        UPDATE storage_object
+        SET write_lock = NULL
+        WHERE ext_id = ?
+    },
+    increment_read_lock => qq{
+        UPDATE storage_object
+        SET read_lock = read_lock+1
+        WHERE ext_id = ?
+    },
+    decrement_read_lock => qq{
+        UPDATE storage_object
+        SET read_lock = read_lock-1
+        WHERE ext_id = ?
+    },
+    delete_instance     => qq{
+        DELETE FROM instance
+        WHERE uri = ?
+    },
+    get_object_from_uri   => qq{
+        SELECT so_id
+        FROM instance
+        WHERE uri = ?
+    },
+    get_instance_count   => qq{
+        SELECT count(ins_id)
+        FROM instance
+        WHERE so_id = ?
+    },
+    get_object_instances    => qq{
+        SELECT storage_object.so_id, uri, assigned_location
+        FROM storage_object
+        LEFT JOIN instance
+        USING (so_id)
+        WHERE storage_object.ext_id = ?
+    },
+);
+
+__PACKAGE__->mk_ro_accessors( keys %sql );
+
+sub new {
+    my $class = shift;
+
+    my $self = \%sql;
+
+    bless $self, $class || ref $class;
+
+    return $self;
+}
+
+1;
+
+__END__
Index: /branches/jh_branches/jhoblitt/Nebulous/lib/Nebulous/Util.pm
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/lib/Nebulous/Util.pm	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/lib/Nebulous/Util.pm	(revision 22254)
@@ -0,0 +1,104 @@
+# Copyright (c) 2004  Joshua Hoblitt
+#
+# $Id: Util.pm,v 1.1.1.1 2004-12-22 02:16:24 jhoblitt Exp $
+
+package PS::IPP::IData::Util;
+
+use strict;
+
+our $VERSION = '0.01';
+
+use base qw( Exporter );
+
+use Log::Log4perl qw( :levels );
+use URI;
+
+my @symbols = qw(
+    %LEVELS
+    _nuke_file
+    _get_file_path
+    _get_filehandle
+    _open_uri
+    _create_empty_file
+);
+
+our @EXPORT_OK      = @symbols;
+our %EXPORT_TAGS    = ( standard => [ @symbols ] );
+
+our %LEVELS = (
+    off     => $OFF,
+    fatal   => $FATAL,
+    error   => $ERROR,
+    warn    => $WARN,
+    info    => $INFO,
+    debug   => $DEBUG,
+    all     => $ALL,
+);
+
+sub _nuke_file {
+    my $path = shift;
+
+    die "can't unlink file $path: it's a directory" 
+        if -d $path;
+    die "can't unlink file $path: it doesn't exist"
+        unless -e $path;
+    unlink $path or die "can't unlink file $path: $!";
+
+    return 1;
+}
+
+sub _get_file_path {
+    my $uri = shift;
+
+    my $path = URI->new( $uri )->path;
+
+    return $path;
+}
+
+sub _get_filehandle {
+    my ( $path, $flags ) = @_;
+
+    die "can't open file $path: file doesn't exist" 
+        unless -e $path;
+    CORE::open( my $fh, $flags, $path )
+        or die "can't open file $path: $!";
+
+    return $fh;
+}
+
+sub _open_uri {
+    my ( $uri , $flags ) = @_;
+
+    my $path = _get_file_path( $uri );
+    my $fh = _get_filehandle( $path, $flags );
+
+    return $fh;
+}
+
+sub _create_empty_file {
+    my $path = shift;
+
+    # perl's open() can't do an O_CREAT | O_EXCL
+    if ( -e $path ) {
+        die "file $path already exists";
+    }
+
+    my $fh;
+    unless ( open( $fh, '>', $path ) ) {
+        die "can not open $path: $!";
+    } 
+
+    unless ( close( $fh ) ) {
+        die "can not close $path: $!";
+    }
+
+    unless ( chmod 0664, $path ) {
+        die "can not chmod $path: $!";
+    }
+
+    return $path;
+}
+
+1;
+
+__END__
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/.cvsignore
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/.cvsignore	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/.cvsignore	(revision 22254)
@@ -0,0 +1,13 @@
+.in
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache
+config.log
+config.status
+configure
+libtool
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/INSTALL
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/INSTALL	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/INSTALL	(revision 22254)
@@ -0,0 +1,8 @@
+Quick Installation
+
+    ./configure
+    make
+    make check
+    make install
+
+Run "configure --help" for additional options.
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/LICENSE
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/LICENSE	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/LICENSE	(revision 22254)
@@ -0,0 +1,23 @@
+Copyright (c) 2004 Nik Clayton
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/Makefile.am	(revision 22254)
@@ -0,0 +1,5 @@
+SUBDIRS  = src
+#SUBDIRS += tests
+
+prove:
+	prove -v -r
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/README
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/README	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/README	(revision 22254)
@@ -0,0 +1,11 @@
+NAME
+     tap -- write tests that implement the Test Anything Protocol
+
+SYNOPSIS
+     #include <tap.h>
+
+DESCRIPTION
+     The tap library provides functions for writing test scripts that produce
+     output consistent with the Test Anything Protocol.  A test harness that
+     parses this protocol can run these tests and produce useful reports indi-
+     cating their success or failure.
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/bootstrap.sh
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/bootstrap.sh	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/bootstrap.sh	(revision 22254)
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+set -x
+aclocal19 -I /usr/local/share/aclocal || aclocal || exit 1
+autoheader259 || autoheader || exit 1
+libtoolize15 -c -f || libtoolize -c -f || glibtoolize -c -f || exit 1
+automake19 -a -c || automake -a -c || exit 1
+autoconf259 || autoconf || exit 1
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/compile
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/compile	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/compile	(revision 22254)
@@ -0,0 +1,142 @@
+#! /bin/sh
+# Wrapper for compilers which do not understand `-c -o'.
+
+scriptversion=2004-10-12.08
+
+# Copyright (C) 1999, 2000, 2003, 2004 Free Software Foundation, Inc.
+# Written by Tom Tromey <tromey@cygnus.com>.
+#
+# 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; either version 2, or (at your option)
+# any later version.
+#
+# 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 this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# This file is maintained in Automake, please report
+# bugs to <bug-automake@gnu.org> or send patches to
+# <automake-patches@gnu.org>.
+
+case $1 in
+  '')
+     echo "$0: No command.  Try \`$0 --help' for more information." 1>&2
+     exit 1;
+     ;;
+  -h | --h*)
+    cat <<\EOF
+Usage: compile [--help] [--version] PROGRAM [ARGS]
+
+Wrapper for compilers which do not understand `-c -o'.
+Remove `-o dest.o' from ARGS, run PROGRAM with the remaining
+arguments, and rename the output as expected.
+
+If you are trying to build a whole package this is not the
+right script to run: please start by reading the file `INSTALL'.
+
+Report bugs to <bug-automake@gnu.org>.
+EOF
+    exit 0
+    ;;
+  -v | --v*)
+    echo "compile $scriptversion"
+    exit 0
+    ;;
+esac
+
+ofile=
+cfile=
+eat=
+
+for arg
+do
+  if test -n "$eat"; then
+    eat=
+  else
+    case $1 in
+      -o)
+	# configure might choose to run compile as `compile cc -o foo foo.c'.
+	# So we strip `-o arg' only if arg is an object.
+	eat=1
+	case $2 in
+	  *.o | *.obj)
+	    ofile=$2
+	    ;;
+	  *)
+	    set x "$@" -o "$2"
+	    shift
+	    ;;
+	esac
+	;;
+      *.c)
+	cfile=$1
+	set x "$@" "$1"
+	shift
+	;;
+      *)
+	set x "$@" "$1"
+	shift
+	;;
+    esac
+  fi
+  shift
+done
+
+if test -z "$ofile" || test -z "$cfile"; then
+  # If no `-o' option was seen then we might have been invoked from a
+  # pattern rule where we don't need one.  That is ok -- this is a
+  # normal compilation that the losing compiler can handle.  If no
+  # `.c' file was seen then we are probably linking.  That is also
+  # ok.
+  exec "$@"
+fi
+
+# Name of file we expect compiler to create.
+cofile=`echo "$cfile" | sed -e 's|^.*/||' -e 's/\.c$/.o/'`
+
+# Create the lock directory.
+# Note: use `[/.-]' here to ensure that we don't use the same name
+# that we are using for the .o file.  Also, base the name on the expected
+# object file name, since that is what matters with a parallel build.
+lockdir=`echo "$cofile" | sed -e 's|[/.-]|_|g'`.d
+while true; do
+  if mkdir "$lockdir" >/dev/null 2>&1; then
+    break
+  fi
+  sleep 1
+done
+# FIXME: race condition here if user kills between mkdir and trap.
+trap "rmdir '$lockdir'; exit 1" 1 2 15
+
+# Run the compile.
+"$@"
+ret=$?
+
+if test -f "$cofile"; then
+  mv "$cofile" "$ofile"
+elif test -f "${cofile}bj"; then
+  mv "${cofile}bj" "$ofile"
+fi
+
+rmdir "$lockdir"
+exit $ret
+
+# Local Variables:
+# mode: shell-script
+# sh-indentation: 2
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-end: "$"
+# End:
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/configure.in
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/configure.in	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/configure.in	(revision 22254)
@@ -0,0 +1,44 @@
+AC_INIT(tap, 1.01)
+AC_CONFIG_SRCDIR(src/tap.c)
+AM_INIT_AUTOMAKE([foreign])
+AC_CONFIG_HEADERS([src/config.h])
+AC_GNU_SOURCE
+AC_PROG_CC
+AC_PROG_LIBTOOL
+AC_PROG_INSTALL
+
+# Checks for libraries
+case "$host" in
+	*-*-*freebsd4*)
+		LDFLAGS="$LDFLAGS -pthread"
+		HAVE_LIBPTHREAD=1
+		;;
+	*)
+		AC_CHECK_LIB(pthread, main)
+		;;
+esac
+
+dnl build tests at the same time as the source code
+AC_ARG_ENABLE(tests,
+  [AS_HELP_STRING(--enable-tests,build tests at same time as source)],
+  [AC_MSG_RESULT(test building enabled)
+   tests=true],
+   [tests=false])
+AM_CONDITIONAL(BUILD_TESTS, test x$tests = xtrue)
+
+# Checks for header files
+AC_HEADER_STDC
+AC_CHECK_HEADERS([stdlib.h])
+AC_CHECK_HEADERS([pthread.h])
+
+# Checks for  typedefs, structures, and compiler characteristics.
+AC_C_CONST
+
+# Checks for library functions.
+AC_FUNC_VPRINTF
+AC_CHECK_FUNCS([atexit])
+
+AC_CONFIG_FILES([Makefile
+		 src/Makefile
+		])
+AC_OUTPUT
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/src/.cvsignore
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/src/.cvsignore	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/src/.cvsignore	(revision 22254)
@@ -0,0 +1,13 @@
+.deps
+.libs
+Makefile
+Makefile.in
+libtap.la
+tap.lo
+config.h
+config.h.in
+stamp-h1
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/src/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/src/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/src/Makefile.am	(revision 22254)
@@ -0,0 +1,9 @@
+TEST_LTLIBS = libtap.la
+libtap_la_SOURCES = tap.c tap.h
+noinst_HEADERS = tap.h
+
+noinst_LTLIBRARIES = $(TEST_LTLIBS)
+
+#man_MANS = tap.3
+EXTRA_DIST = $(man_MANS)
+
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/src/tap.3
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/src/tap.3	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/src/tap.3	(revision 22254)
@@ -0,0 +1,380 @@
+.Dd December 20, 2004
+.Os
+.Dt TAP 3
+.Sh NAME
+.Nm tap
+.Nd write tests that implement the Test Anything Protocol
+.Sh SYNOPSIS
+.In tap.h
+.Sh DESCRIPTION
+The
+.Nm
+library provides functions for writing test scripts that produce output
+consistent with the Test Anything Protocol.  A test harness that parses
+this protocol can run these tests and produce useful reports indicating
+their success or failure.
+.Ss PRINTF STRINGS
+In the descriptions that follow, for any function that takes as the
+last two parameters
+.Dq Fa char * , Fa ...
+it can be assumed that the
+.Fa char *
+is a
+.Fn printf
+-like format string, and the optional arguments are values to be placed
+in that string.
+.Ss TEST PLANS
+.Bl -tag -width indent
+.It Xo
+.Ft int
+.Fn plan_tests "unsigned int"
+.Xc
+.It Xo
+.Ft int
+.Fn plan_no_plan "void"
+.Xc
+.It Xo
+.Ft int
+.Fn plan_skip_all "char *" "..."
+.Xc
+.El
+.Pp
+You must first specify a test plan.  This indicates how many tests you
+intend to run, and allows the test harness to notice if any tests were
+missed, or if the test program exited prematurely.
+.Pp
+To do this, use
+.Fn plan_tests ,
+which returns the number of planned tests.  The function will cause
+your program to exit prematurely if you specify 0 tests.
+.Pp
+In some situations you may not know how many tests you will be running, or
+you are developing your test program, and do not want to update the
+.Fn plan_tests
+parameter every time you make a change.  For those situations use
+.Fn plan_no_plan .
+It returns 1, and indicates to the test harness that an indeterminate number
+of tests will be run.
+.Pp
+Both
+.Fn plan_tests
+and
+.Fn plan_no_plan
+will cause your test program to exit prematurely with a diagnostic
+message if they are called more than once.
+.Pp
+If your test program detects at run time that some required functionality
+is missing (for example, it relies on a database connection which is not
+present, or a particular configuration option that has not been included
+in the running kernel) use
+.Fn plan_skip_all ,
+passing as parameters a string to display indicating the reason for skipping
+the tests.
+.Ss SIMPLE TESTS
+.Bl -tag -width indent
+.It Xo
+.Ft unsigned int
+.Fn ok "expression" "char *" "..."
+.Xc
+.It Xo
+.Ft unsigned int
+.Fn ok1 "expression"
+.Xc
+.It Xo
+.Ft unsigned int
+.Fn pass "char *" "..."
+.Xc
+.It Xo
+.Ft unsigned int
+.Fn fail "char *" "..."
+.Xc
+.El
+.Pp
+Tests are implemented as expressions checked by calls to the
+.Fn ok
+and
+.Fn ok1
+macros.  In both cases
+.Fa expression
+should evaluate to true if the test succeeded.
+.Pp
+.Fn ok
+allows you to specify a name, or comment, describing the test which will
+be included in the output.
+.Fn ok1
+is for those times when the expression to be tested is self
+explanatory and does not need an associated comment.  In those cases
+the test expression becomes the comment.
+.Pp
+These four calls are equivalent:
+.Bd -literal -offset indent
+int i = 5;
+
+ok(i == 5, "i equals 5");      /* Overly verbose */
+ok(i == 5, "i equals %d", i);  /* Just to demonstrate printf-like
+                                  behaviour of the test name */
+ok(i == 5, "i == 5");          /* Needless repetition */
+ok1(i == 5);                   /* Just right */
+.Ed
+.Pp
+It is good practice to ensure that the test name describes the meaning
+behind the test rather than what you are testing.  Viz
+.Bd -literal -offset indent
+ok(db != NULL, "db is not NULL");            /* Not bad, but */
+ok(db != NULL, "Database conn. succeeded");  /* this is better */
+.Ed
+.Pp
+.Fn ok
+and
+.Fn ok1
+return 1 if the expression evaluated to true, and 0 if it evaluated to
+false.  This lets you chain calls from
+.Fn ok
+to
+.Fn diag
+to only produce diagnostic output if the test failed.  For example, this
+code will include diagnostic information about why the database connection
+failed, but only if the test failed.
+.Bd -literal -offset indent
+ok(db != NULL, "Database conn. succeeded") ||
+    diag("Database error code: %d", dberrno);
+.Ed
+.Pp
+You also have
+.Fn pass
+and
+.Fn fail .
+From the Test::More documentation:
+.Bd -literal -offset indent
+Sometimes you just want to say that the tests have passed.
+Usually the case is you've got some complicated condition
+that is difficult to wedge into an ok().  In this case,
+you can simply use pass() (to declare the test ok) or fail
+(for not ok).
+
+Use these very, very, very sparingly.
+.Ed
+.Pp
+These are synonyms for ok(1, ...) and ok(0, ...).
+.Ss SKIPPING TESTS
+.Bl -tag -width indent
+.It Xo
+.Ft int
+.Fn skip "unsigned int" "char *" "..."
+.Xc
+.It Xo
+.Fn skip_start "expression" "unsigned int" "char *" "..."
+.Xc
+.It Xo
+.Fn skip_end
+.Xc
+.El
+.Pp
+Sets of tests can be skipped.  Ordinarily you would do this because
+the test can't be run in this particular testing environment.
+.Pp
+For example, suppose some tests should be run as root.  If the test is
+not being run as root then the tests should be skipped.  In this 
+implementation, skipped tests are flagged as being ok, with a special
+message indicating that they were skipped.  It is your responsibility
+to ensure that the number of tests skipped (the first parameter to
+.Fn skip )
+is correct for the number of tests to skip.
+.Pp
+One way of implementing this is with a
+.Dq do { } while(0);
+loop, or an
+.Dq if( ) { } else { }
+construct, to ensure that there are no additional side effects from the
+skipped tests.
+.Bd -literal -offset indent
+if(getuid() != 0) {
+        skip(1, "because test only works as root");
+} else {
+        ok(do_something_as_root() == 0, "Did something as root");
+}
+.Ed
+.Pp
+Two macros are provided to assist with this.  The previous example could
+be re-written as follows.
+.Bd -literal -offset indent
+skip_start(getuid() != 0, 1, "because test only works as root");
+
+ok(do_something_as_root() == 0, "Did something as root");
+
+skip_end();
+.Ed
+.Ss MARKING TESTS AS Dq TODO
+.Bl -tag -width indent
+.It Xo
+.Ft void
+.Fn todo_start "char *" "..."
+.Xc
+.It Xo
+.Ft void
+.Fn todo_end "void"
+.Xc
+.El
+.Pp
+Sets of tests can be flagged as being
+.Dq TODO .
+These are tests that you expect to fail, probably because you haven't
+fixed a bug, or finished a new feature yet.  These tests will still be
+run, but with additional output that indicates that they are expected
+to fail.  Should a test start to succeed unexpectedly, tools like
+.Xr prove 1
+will indicate this, and you can move the test out of the todo
+block.  This is much more useful than simply commenting out (or
+.Dq #ifdef 0 ... #endif )
+the tests.
+.Bd -literal -offset indent
+todo_start("dwim() not returning true yet");
+
+ok(dwim(), "Did what the user wanted");
+
+todo_end();
+.Ed
+.Pp
+Should
+.Fn dwim
+ever start succeeding you will know about it as soon as you run the
+tests.  Note that
+.Em unlike
+the
+.Fn skip_*
+family, additional code between
+.Fn todo_start
+and
+.Fn todo_end
+.Em is
+executed.
+.Ss SKIP vs. TODO
+From the Test::More documentation;
+.Bd -literal -offset indent
+If it's something the user might not be able to do, use SKIP.
+This includes optional modules that aren't installed, running
+under an OS that doesn't have some feature (like fork() or
+symlinks), or maybe you need an Internet connection and one
+isn't available.
+
+If it's something the programmer hasn't done yet, use TODO.
+This is for any code you haven't written yet, or bugs you have
+yet to fix, but want to put tests in your testing script 
+(always a good idea).
+.Ed
+.Ss DIAGNOSTIC OUTPUT
+.Bl -tag -width indent
+.It Xo
+.Fr unsigned int
+.Fn diag "char *" "..."
+.Xc
+.El
+.Pp
+If your tests need to produce diagnostic output, use
+.Fn diag .
+It ensures that the output will not be considered by the TAP test harness.
+.Fn diag
+adds the necessary trailing
+.Dq \en
+for you.
+.Bd -literal -offset indent
+diag("Expected return code 0, got return code %d", rcode);
+.Ed
+.Pp
+.Fn diag
+always returns 0.
+.Ss EXIT STATUS
+.Bl -tag -width indent
+.It Xo
+.Fr int
+.Fn exit_status void
+.Xc
+.El
+.Pp
+For maximum compatability your test program should return a particular
+exit code.  This is calculated by
+.Fn exit_status
+so it is sufficient to always return from
+.Fn main
+with either
+.Dq return exit_status();
+or
+.Dq exit(exit_status());
+as appropriate.
+.Sh ENVIRONMENT
+The following environment variables affect
+.Nm .
+.Bl -tag -width indent
+.It Ev HARNESS_ACTIVE
+Causes an extra
+.Dq \en
+to be printed before any diagnostic failure output generated by
+.Nm .
+This variable is normally set if tests are being run under Perl's
+Test::Harness.
+.El
+.Sh EXAMPLES
+The
+.Pa tests
+directory in the source distribution contains numerous tests of
+.Nm
+functionality, written using
+.Nm .
+Examine them for examples of how to construct test suites.
+.Sh COMPATABILITY
+.Nm
+strives to be compatible with the Perl Test::More and Test::Harness 
+modules.  The test suite verifies that
+.Nm
+is bug-for-bug compatible with their behaviour.  This is why some
+functions which would more naturally return nothing return constant
+values.
+.Pp
+If the
+.Lb libpthread
+is found at compile time,
+.Nm
+.Em should
+be thread safe.  Indications to the contrary (and test cases that expose
+incorrect behaviour) are very welcome.
+.Sh SEE ALSO
+.Xr Test::More 1 ,
+.Xr Test::Harness 1 ,
+.Xr prove 1
+.Sh STANDARDS
+.Nm
+requires a
+.St -isoC-99
+compiler.  Some of the
+.Nm
+functionality is implemented as variadic macros, and that functionality
+was not formally codified until C99.  Patches to use
+.Nm
+with earlier compilers that have their own implementation of variadic
+macros will be gratefully received.
+.Sh HISTORY
+.Nm
+was written to help improve the quality and coverage of the FreeBSD
+regression test suite, and released in the hope that others find it
+a useful tool to help improve the quality of their code.
+.Sh AUTHORS
+.An "Nik Clayton" Aq nik@ngo.org.uk ,
+.Aq nik@FreeBSD.org
+.Pp
+.Nm
+would not exist without the efforts of
+.An "Michael G Schwern" Aq schqern@pobox.com ,
+.An "Andy Lester" Aq andy@petdance.com ,
+and the countless others who have worked on the Perl QA programme.
+.Sh BUGS
+Ideally, running the tests would have no side effects on the behaviour
+of the application you are testing.  However, it is not always possible
+to avoid them.  The following side effects of using
+.Nm
+are known.
+.Bl -bullet -offset indent
+.It
+stdout is set to unbuffered mode after calling any of the
+.Fn plan_*
+functions.
+.El
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/src/tap.c
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/src/tap.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/src/tap.c	(revision 22254)
@@ -0,0 +1,430 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "tap.h"
+
+static int no_plan = 0;
+static int skip_all = 0;
+static int have_plan = 0;
+static unsigned int test_count = 0; /* Number of tests that have been run */
+static unsigned int e_tests = 0; /* Expected number of tests to run */
+static unsigned int failures = 0; /* Number of tests that failed */
+static char *todo_msg = NULL;
+static char *todo_msg_fixed = "libtap malloc issue";
+static int todo = 0;
+static int test_died = 0;
+
+/* Encapsulate the pthread code in a conditional.  In the absence of
+   libpthread the code does nothing */
+#ifdef HAVE_LIBPTHREAD
+#include <pthread.h>
+static pthread_mutex_t M = PTHREAD_MUTEX_INITIALIZER;
+# define LOCK pthread_mutex_lock(&M);
+# define UNLOCK pthread_mutex_unlock(&M);
+#else
+# define LOCK
+# define UNLOCK
+#endif
+
+static void _expected_tests(unsigned int);
+static void _tap_init(void);
+static void _cleanup(void);
+
+/*
+ * Generate a test result.
+ *
+ * ok -- boolean, indicates whether or not the test passed.
+ * test_name -- the name of the test, may be NULL
+ * test_comment -- a comment to print afterwards, may be NULL
+ */
+unsigned int
+_gen_result(int ok, const char *func, char *file, unsigned int line,
+            char *test_name, ...)
+{
+    va_list ap;
+    char *local_test_name = NULL;
+    char *c;
+    int name_is_digits;
+
+    LOCK;
+
+    test_count++;
+
+    /* Start by taking the test name and performing any printf()
+       expansions on it */
+    if(test_name != NULL) {
+        va_start(ap, test_name);
+        vasprintf(&local_test_name, test_name, ap);
+        va_end(ap);
+
+        /* Make sure the test name contains more than digits
+           and spaces.  Emit an error message and exit if it
+           does */
+        if(local_test_name) {
+            name_is_digits = 1;
+            for(c = local_test_name; *c != '\0'; c++) {
+                if(!isdigit(*c) && !isspace(*c)) {
+                    name_is_digits = 0;
+                    break;
+                }
+            }
+
+            if(name_is_digits) {
+                diag("    You named your test '%s'.  You shouldn't use numbers for your test names.", local_test_name);
+                diag("    Very confusing.");
+            }
+        }
+    }
+
+    if(!ok) {
+        printf("not ");
+        failures++;
+    }
+
+    printf("ok %d", test_count);
+
+    if(test_name != NULL) {
+        printf(" - ");
+
+        /* Print the test name, escaping any '#' characters it
+           might contain */
+        if(local_test_name != NULL) {
+            flockfile(stdout);
+            for(c = local_test_name; *c != '\0'; c++) {
+                if(*c == '#')
+                    fputc('\\', stdout);
+                fputc((int)*c, stdout);
+            }
+            funlockfile(stdout);
+        } else { /* vasprintf() failed, use a fixed message */
+            printf("%s", todo_msg_fixed);
+        }
+    }
+
+    /* If we're in a todo_start() block then flag the test as being
+       TODO.  todo_msg should contain the message to print at this
+       point.  If it's NULL then asprintf() failed, and we should
+       use the fixed message.
+
+       This is not counted as a failure, so decrement the counter if
+       the test failed. */
+    if(todo) {
+        printf(" # TODO %s", todo_msg ? todo_msg : todo_msg_fixed);
+        if(!ok)
+            failures--;
+    }
+
+    printf("\n");
+
+    if(!ok) {
+        if(getenv("HARNESS_ACTIVE") != NULL)
+            fputs("\n", stderr);
+
+        diag("    Failed %stest (%s:%s() at line %d)",
+             todo ? "(TODO) " : "", file, func, line);
+    }
+    free(local_test_name);
+
+    UNLOCK;
+
+    /* We only care (when testing) that ok is positive, but here we
+       specifically only want to return 1 or 0 */
+    return ok ? 1 : 0;
+}
+
+/*
+ * Initialise the TAP library.  Will only do so once, however many times it's
+ * called.
+ */
+void
+_tap_init(void)
+{
+    static int run_once = 0;
+
+    if(!run_once) {
+        atexit(_cleanup);
+
+        /* stdout needs to be unbuffered so that the output appears
+           in the same place relative to stderr output as it does 
+           with Test::Harness */
+        setbuf(stdout, 0);
+        run_once = 1;
+    }
+}
+
+/*
+ * Note that there's no plan.
+ */
+int
+plan_no_plan(void)
+{
+
+    LOCK;
+
+    _tap_init();
+
+    if(have_plan != 0) {
+        fprintf(stderr, "You tried to plan twice!\n");
+        test_died = 1;
+        UNLOCK;
+        exit(255);
+    }
+
+    have_plan = 1;
+    no_plan = 1;
+
+    UNLOCK;
+
+    return 1;
+}
+
+/*
+ * Note that the plan is to skip all tests
+ */
+int
+plan_skip_all(char *reason)
+{
+
+    LOCK;
+
+    _tap_init();
+
+    skip_all = 1;
+
+    printf("1..0");
+
+    if(reason != NULL)
+        printf(" # Skip %s", reason);
+
+    printf("\n");
+
+    UNLOCK;
+
+    exit(0);
+}
+
+/*
+ * Note the number of tests that will be run.
+ */
+int
+plan_tests(unsigned int tests)
+{
+
+    LOCK;
+
+    _tap_init();
+
+    if(have_plan != 0) {
+        fprintf(stderr, "You tried to plan twice!\n");
+        test_died = 1;
+        UNLOCK;
+        exit(255);
+    }
+
+    if(tests == 0) {
+        fprintf(stderr, "You said to run 0 tests!  You've got to run something.\n");
+        test_died = 1;
+        UNLOCK;
+        exit(255);
+    }
+
+    have_plan = 1;
+
+    _expected_tests(tests);
+
+    UNLOCK;
+
+    return e_tests;
+}
+
+unsigned int
+diag(char *fmt, ...)
+{
+    va_list ap;
+
+    fputs("# ", stderr);
+
+    va_start(ap, fmt);
+    vfprintf(stderr, fmt, ap);
+    va_end(ap);
+
+    fputs("\n", stderr);
+
+    return 0;
+}
+
+void
+_expected_tests(unsigned int tests)
+{
+
+    printf("1..%d\n", tests);
+    e_tests = tests;
+}
+
+int
+skip(unsigned int n, char *fmt, ...)
+{
+    va_list ap;
+    char *skip_msg;
+
+    LOCK;
+
+    va_start(ap, fmt);
+    asprintf(&skip_msg, fmt, ap);
+    va_end(ap);
+
+    while(n-- > 0) {
+        test_count++;
+        printf("ok %d # skip %s\n", test_count,
+               skip_msg != NULL ?
+               skip_msg : "libtap():malloc() failed");
+    }
+
+    free(skip_msg);
+
+    UNLOCK;
+
+    return 1;
+}
+
+void
+todo_start(char *fmt, ...)
+{
+    va_list ap;
+
+    LOCK;
+
+    va_start(ap, fmt);
+    vasprintf(&todo_msg, fmt, ap);
+    va_end(ap);
+
+    todo = 1;
+
+    UNLOCK;
+}
+
+void
+todo_end(void)
+{
+
+    LOCK;
+
+    todo = 0;
+    free(todo_msg);
+
+    UNLOCK;
+}
+
+int
+exit_status(void)
+{
+    int r;
+
+    LOCK;
+
+    /* If there's no plan, just return the number of failures */
+    if(no_plan || !have_plan) {
+        UNLOCK;
+        return failures;
+    }
+
+    /* Ran too many tests?  Return the number of tests that were run
+       that shouldn't have been */
+    if(e_tests < test_count) {
+        r = test_count - e_tests;
+        UNLOCK;
+        return r;
+    }
+
+    /* Return the number of tests that failed + the number of tests
+       that weren't run */
+    r = failures + e_tests - test_count;
+    UNLOCK;
+
+    return r;
+}
+
+/*
+ * Cleanup at the end of the run, produce any final output that might be
+ * required.
+ */
+void
+_cleanup(void)
+{
+
+    LOCK;
+
+    /* If plan_no_plan() wasn't called, and we don't have a plan,
+       and we're not skipping everything, then something happened
+       before we could produce any output */
+    if(!no_plan && !have_plan && !skip_all) {
+        diag("Looks like your test died before it could output anything.");
+        UNLOCK;
+        return;
+    }
+
+    if(test_died) {
+        diag("Looks like your test died just after %d.", test_count);
+        UNLOCK;
+        return;
+    }
+
+
+    /* No plan provided, but now we know how many tests were run, and can
+       print the header at the end */
+    if(!skip_all && (no_plan || !have_plan)) {
+        printf("1..%d\n", test_count);
+    }
+
+    if((have_plan && !no_plan) && e_tests < test_count) {
+        diag("Looks like you planned %d %s but ran %d extra.",
+             e_tests, e_tests == 1 ? "test" : "tests", test_count - e_tests);
+        UNLOCK;
+        return;
+    }
+
+    if((have_plan || !no_plan) && e_tests > test_count) {
+        diag("Looks like you planned %d %s but only ran %d.",
+             e_tests, e_tests == 1 ? "test" : "tests", test_count);
+        UNLOCK;
+        return;
+    }
+
+    if(failures)
+        diag("Looks like you failed %d %s of %d.",
+             failures, failures == 1 ? "test" : "tests", test_count);
+
+    UNLOCK;
+}
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/src/tap.h
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/src/tap.h	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/src/tap.h	(revision 22254)
@@ -0,0 +1,89 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* '## __VA_ARGS__' is a gcc'ism. C99 doesn't allow the token pasting
+   and requires the caller to add the final comma if they've ommitted
+   the optional arguments */
+#ifdef __GNUC__
+# define ok(e, test, ...) ((e) ?					\
+			   _gen_result(1, __func__, __FILE__, __LINE__,	\
+				       test, ## __VA_ARGS__) :		\
+			   _gen_result(0, __func__, __FILE__, __LINE__,	\
+				       test, ## __VA_ARGS__))
+
+# define ok1(e) ((e) ?							\
+		 _gen_result(1, __func__, __FILE__, __LINE__, "%s", #e) : \
+		 _gen_result(0, __func__, __FILE__, __LINE__, "%s", #e))
+
+# define pass(test, ...) ok(1, test, ## __VA_ARGS__);
+# define fail(test, ...) ok(0, test, ## __VA_ARGS__);
+
+# define skip_start(test, n, fmt, ...)			\
+	do {						\
+		if((test)) {				\
+			skip(n, fmt, ## __VA_ARGS__);	\
+			continue;			\
+		}
+#elif __STDC_VERSION__ >= 199901L /* __GNUC__ */
+# define ok(e, ...) ((e) ?						\
+		     _gen_result(1, __func__, __FILE__, __LINE__,	\
+				 __VA_ARGS__) :				\
+		     _gen_result(0, __func__, __FILE__, __LINE__,	\
+				 __VA_ARGS__))
+
+# define ok1(e) ((e) ?							\
+		 _gen_result(1, __func__, __FILE__, __LINE__, "%s", #e) : \
+		 _gen_result(0, __func__, __FILE__, __LINE__, "%s", #e))
+
+# define pass(...) ok(1, __VA_ARGS__);
+# define fail(...) ok(0, __VA_ARGS__);
+
+# define skip_start(test, n, ...)			\
+	do {						\
+		if((test)) {				\
+			skip(n,  __VA_ARGS__);		\
+			continue;			\
+		}
+#else /* __STDC_VERSION__ */
+# error "Needs gcc or C99 compiler for variadic macros."
+#endif /* __STDC_VERSION__ */
+
+#define skip_end() } while(0);
+
+unsigned int _gen_result(int, const char *, char *, unsigned int, char *, ...);
+
+int plan_no_plan(void);
+int plan_skip_all(char *);
+int plan_tests(unsigned int);
+
+unsigned int diag(char *, ...);
+
+int skip(unsigned int, char *, ...);
+
+void todo_start(char *, ...);
+void todo_end(void);
+
+int exit_status(void);
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/.cvsignore
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/.cvsignore	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/.cvsignore	(revision 22254)
@@ -0,0 +1,6 @@
+Makefile
+Makefile.in
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/Makefile.am	(revision 22254)
@@ -0,0 +1,7 @@
+SUBDIRS=	diag
+SUBDIRS+=	fail
+SUBDIRS+=	ok
+SUBDIRS+=	pass
+SUBDIRS+=	plan
+SUBDIRS+=	skip
+SUBDIRS+=	todo
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/README
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/README	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/README	(revision 22254)
@@ -0,0 +1,12 @@
+Most of the tests follow the same pattern.
+
+ * test.pl that uses Test::More, and demonstrates whatever functionality 
+   that we're trying to test.  This is the reference code.
+
+ * test.c, which tests the libtap reimplementation of the same functionality.
+
+ * test.t, which compiles the .c program, runs both test scripts, and then 
+   diffs their output to make sure it's identical.
+
+   Right now, test.t is identical in every directory.  This sucks somewhat.
+   It should either be a symlink to a common script
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/diag/.cvsignore
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/diag/.cvsignore	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/diag/.cvsignore	(revision 22254)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/diag/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/diag/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/diag/Makefile.am	(revision 22254)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/diag/test.c
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/diag/test.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/diag/test.c	(revision 22254)
@@ -0,0 +1,46 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+	unsigned int rc = 0;
+
+	plan_tests(2);
+
+	rc = diag("A diagnostic message");
+	diag("Returned: %d", rc);
+
+	/* Make sure the failure is passed through */
+	ok(1, "test 1") || diag("ok() failed, and shouldn't");
+	ok(0, "test 2") || diag("ok() passed, and shouldn't");
+
+	return exit_status();
+}
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/diag/test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/diag/test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/diag/test.pl	(revision 22254)
@@ -0,0 +1,16 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+plan tests => 2;
+
+$rc = diag("A diagnostic message");
+diag("Returned: $rc");
+
+ok(1, 'test 1') or diag "ok() failed, and shouldn't";
+ok(0, 'test 2') or diag "ok() passed, and shouldn't";
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/diag/test.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/diag/test.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/diag/test.t	(revision 22254)
@@ -0,0 +1,67 @@
+#!/bin/sh
+
+echo '1..7'
+
+perl $srcdir/test.pl 2>/dev/null >test.pl.out
+perlstatus=$?
+
+./test > test.c.out 2>&1
+cstatus=$?
+
+if grep "^# A diagnostic message$" test.c.out > /dev/null ; then
+    echo "ok 1 - found a diagnostic message"
+else
+    echo "not ok 1 - found a diagnostic message"
+    retval=1
+fi
+
+if grep "^# Returned: 0$" test.c.out > /dev/null ; then
+    echo "ok 2 - diag() expected return value" 
+else
+    echo "not ok 2 - diag() expected return value" 
+    retval=1
+fi
+
+if grep "^#     Failed test (.*test.c:main() at line 43)$" test.c.out > /dev/null ; then
+    echo "ok 3 - 'failed test at line' diag" 
+else
+    echo "not ok 3 - 'failed test at line' diag" 
+    retval=1
+fi
+
+if grep "^# ok() passed, and shouldn't$" test.c.out > /dev/null ; then
+    echo "ok 4 - expected diag"
+else
+    echo "ok 4 - expected diag"
+    retval=1
+fi
+
+if grep "^# Looks like you failed 1 test of 2.$" test.c.out > /dev/null ; then
+    echo "ok 5 - failed 1 test"
+ else
+    echo "ok 5 - failed 1 test"
+    retval=1
+fi
+
+sed -e '/^#/D' test.c.out > tmp
+mv tmp test.c.out
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 6 - TAP output is identical'
+else
+	retval=1
+	echo 'not ok 6 - TAP output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 7 - status code'
+else
+	retval=1
+	echo 'not ok 7 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/fail/.cvsignore
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/fail/.cvsignore	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/fail/.cvsignore	(revision 22254)
@@ -0,0 +1,15 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/fail/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/fail/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/fail/Makefile.am	(revision 22254)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/fail/test.c
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/fail/test.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/fail/test.c	(revision 22254)
@@ -0,0 +1,46 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+	unsigned int rc = 0;
+
+	rc = plan_tests(2);
+	diag("Returned: %d", rc);
+
+	rc = fail("test to fail");
+	diag("Returned: %d", rc);
+
+	rc = fail("test to fail %s", "with extra string");
+	diag("Returned: %d", rc);
+
+	return exit_status();
+}
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/fail/test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/fail/test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/fail/test.pl	(revision 22254)
@@ -0,0 +1,17 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 2;
+diag("Returned: " . sprintf('%d', $rc));
+
+$rc = fail('test to fail');
+diag("Returned: $rc");
+
+$rc = fail('test to fail with extra string');
+diag("Returned: $rc");
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/fail/test.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/fail/test.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/fail/test.t	(revision 22254)
@@ -0,0 +1,74 @@
+#!/bin/sh
+
+echo '1..8'
+
+perl $srcdir/test.pl 2>/dev/null > test.pl.out
+perlstatus=$?
+
+./test > test.c.out 2>&1 
+cstatus=$?
+
+if grep "^# Returned: 2$" test.c.out >/dev/null ; then
+  echo "ok 1 - expected return value";
+else
+  echo "not ok 1 - expected return value";
+  retval=1
+fi
+
+if grep "^#     Failed test (.*test.c:main() at line 39)$" test.c.out >/dev/null ; then
+  echo "ok 2 - failed expected test";
+else
+  echo "not ok 2 - failed expected test";
+  retval=1
+fi
+
+if grep "^# Returned: 0$" test.c.out >/dev/null ; then
+  echo "ok 3 - expected return value";
+else
+  echo "not ok 3 - expected return value";
+  retval=1
+fi
+
+if grep "^#     Failed test (.*test.c:main() at line 42)$" test.c.out >/dev/null ; then
+  echo "ok 4 - failed expected test";
+else
+  echo "not ok 4 - failed expected test";
+  retval=1
+fi
+  
+if grep "^# Returned: 0$" test.c.out >/dev/null ; then
+  echo "ok 5 - expected return value";
+else
+  echo "not ok 5 - expected return value";
+  retval=1
+fi
+
+if grep "^# Looks like you failed 2 tests of 2.$" test.c.out >/dev/null ; then
+  echo "ok 6 - expected return value";
+else
+  echo "not ok 6 - expected return value";
+  retval=1
+fi
+
+sed -e '/^#/D' test.c.out > tmp
+mv tmp test.c.out
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 7 - output is identical'
+else
+	retval=1
+	echo 'not ok 7 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 8 - status code'
+else
+	retval=1
+	echo 'not ok 8 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/.cvsignore
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/.cvsignore	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/.cvsignore	(revision 22254)
@@ -0,0 +1,6 @@
+Makefile
+Makefile.in
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/Makefile.am	(revision 22254)
@@ -0,0 +1,3 @@
+SUBDIRS  =	ok
+SUBDIRS +=	ok-hash
+SUBDIRS +=	ok-numeric
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok-hash/.cvsignore
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok-hash/.cvsignore	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok-hash/.cvsignore	(revision 22254)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok-hash/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok-hash/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok-hash/Makefile.am	(revision 22254)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok-hash/test.c
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok-hash/test.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok-hash/test.c	(revision 22254)
@@ -0,0 +1,52 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+	unsigned int rc = 0;
+
+	rc = plan_tests(4);
+	diag("Returned: %d", rc);
+
+	rc = ok(1, "Test with no hash");
+	diag("Returned: %d", rc);
+
+	rc = ok(1, "Test with one # hash");
+	diag("Returned: %d", rc);
+
+        rc = ok(1, "Test with # two # hashes");
+	diag("Returned: %d", rc);
+
+	rc = ok(1, "Test with ## back to back hashes");
+	diag("Returned: %d", rc);
+	
+	return exit_status();
+}
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok-hash/test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok-hash/test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok-hash/test.pl	(revision 22254)
@@ -0,0 +1,24 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 4;
+diag("Returned: " . sprintf("%d", $rc));
+
+
+$rc = ok(1, 'Test with no hash');
+diag("Returned: $rc");
+
+$rc = ok(1, 'Test with one # hash');
+diag("Returned: $rc");
+
+$rc = ok(1, 'Test with # two # hashes');
+diag("Returned: $rc");
+
+$rc = ok(1, 'Test with ## back to back hashes');
+diag("Returned: $rc");
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok-hash/test.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok-hash/test.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok-hash/test.t	(revision 22254)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok-numeric/.cvsignore
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok-numeric/.cvsignore	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok-numeric/.cvsignore	(revision 22254)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok-numeric/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok-numeric/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok-numeric/Makefile.am	(revision 22254)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok-numeric/test.c
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok-numeric/test.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok-numeric/test.c	(revision 22254)
@@ -0,0 +1,49 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+	unsigned int rc = 0;
+
+	rc = plan_tests(3);
+	diag("Returned: %d", rc);
+
+	rc = ok(1, "First test");
+	diag("Returned: %d", rc);
+
+	rc = ok(1, "1");
+	diag("Returned: %d", rc);
+
+	rc = ok(1, "Third test");
+	diag("Returned: %d", rc);
+
+	return exit_status();
+}
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok-numeric/test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok-numeric/test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok-numeric/test.pl	(revision 22254)
@@ -0,0 +1,21 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 3;
+diag("Returned: " . sprintf("%d", $rc));
+
+
+$rc = ok(1, 'First test');
+diag("Returned: $rc");
+
+$rc = ok(1, '1');
+diag("Returned: $rc");
+
+$rc = ok(1, 'Third test');
+diag("Returned: $rc");
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok-numeric/test.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok-numeric/test.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok-numeric/test.t	(revision 22254)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok/.cvsignore
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok/.cvsignore	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok/.cvsignore	(revision 22254)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok/Makefile.am	(revision 22254)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok/test.c
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok/test.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok/test.c	(revision 22254)
@@ -0,0 +1,55 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+	unsigned int rc = 0;
+
+	rc = plan_tests(5);
+	diag("Returned: %d", rc);
+
+	rc = ok(1 == 1, "1 equals 1");
+	diag("Returned: %d", rc);
+
+	rc = ok(1 == 1, "1 equals %d", 1);
+	diag("Returned: %d", rc);
+
+	rc = ok1(1 == 1);
+	diag("Returned: %d", rc);
+
+	rc = ok(1 == 2, "1 equals 2");
+	diag("Returned: %d", rc);
+
+	rc = ok1(1 == 2);
+	diag("Returned: %d", rc);
+
+	return exit_status();
+}
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok/test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok/test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok/test.pl	(revision 22254)
@@ -0,0 +1,27 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 5;
+diag("Returned: " . sprintf("%d", $rc));
+
+
+$rc = ok(1 == 1, '1 equals 1');	# Test ok() passes when it should
+diag("Returned: $rc");
+
+$rc = ok(1 == 1, '1 equals 1'); # Used for %d testing in test.c
+diag("Returned: $rc");
+
+$rc = ok(1 == 1, '1 == 1');	# Test ok1() passes when it should
+diag("Returned: $rc");
+
+$rc = ok(1 == 2, '1 equals 2');	# Test ok() fails when it should
+diag("Returned: $rc");
+
+$rc = ok(1 == 2, '1 == 2');	# Test ok1() fails when it should
+diag("Returned: $rc");
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok/test.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok/test.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/ok/ok/test.t	(revision 22254)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/pass/.cvsignore
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/pass/.cvsignore	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/pass/.cvsignore	(revision 22254)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/pass/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/pass/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/pass/Makefile.am	(revision 22254)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/pass/test.c
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/pass/test.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/pass/test.c	(revision 22254)
@@ -0,0 +1,46 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+	unsigned int rc = 0;
+
+	rc = plan_tests(2);
+	diag("Returned: %d", rc);
+
+	rc = pass("test to pass");
+	diag("Returned: %d", rc);
+
+	rc = pass("test to pass %s", "with extra string");
+	diag("Returned: %d", rc);
+
+	return exit_status();
+}
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/pass/test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/pass/test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/pass/test.pl	(revision 22254)
@@ -0,0 +1,17 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 2;
+diag("Returned: " . sprintf('%d', $rc));
+
+$rc = pass('test to pass');
+diag("Returned: $rc");
+
+$rc = pass('test to pass with extra string');
+diag("Returned: $rc");
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/pass/test.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/pass/test.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/pass/test.t	(revision 22254)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/.cvsignore
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/.cvsignore	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/.cvsignore	(revision 22254)
@@ -0,0 +1,6 @@
+Makefile
+Makefile.in
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/Makefile.am	(revision 22254)
@@ -0,0 +1,7 @@
+SUBDIRS  =	no-tests
+SUBDIRS +=	no_plan
+SUBDIRS +=	not-enough-tests
+SUBDIRS +=	too-many-plans
+SUBDIRS +=	too-many-tests
+SUBDIRS +=	sane
+SUBDIRS +=	skip_all
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/no-tests/.cvsignore
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/no-tests/.cvsignore	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/no-tests/.cvsignore	(revision 22254)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/no-tests/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/no-tests/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/no-tests/Makefile.am	(revision 22254)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/no-tests/test.c
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/no-tests/test.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/no-tests/test.c	(revision 22254)
@@ -0,0 +1,43 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+	unsigned int rc = 0;
+
+	rc = plan_tests(0);
+	diag("Returned: %d", rc);
+
+	rc = ok(1, NULL);
+	diag("Returned: %d", rc);
+
+	return exit_status();
+}
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/no-tests/test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/no-tests/test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/no-tests/test.pl	(revision 22254)
@@ -0,0 +1,14 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 0;
+diag("Returned: " . sprintf("%d", $rc));
+
+$rc = ok(1);
+diag("Returned: $rc");
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/no-tests/test.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/no-tests/test.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/no-tests/test.t	(revision 22254)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/no_plan/.cvsignore
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/no_plan/.cvsignore	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/no_plan/.cvsignore	(revision 22254)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/no_plan/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/no_plan/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/no_plan/Makefile.am	(revision 22254)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/no_plan/test.c
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/no_plan/test.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/no_plan/test.c	(revision 22254)
@@ -0,0 +1,43 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+	unsigned int rc = 0;
+
+	rc = plan_no_plan();
+	diag("Returned: %d", rc);
+
+	rc = ok(1, NULL);
+	diag("Returned: %d", rc);
+
+	return exit_status();
+}
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/no_plan/test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/no_plan/test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/no_plan/test.pl	(revision 22254)
@@ -0,0 +1,14 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+my $rc = 0;
+
+use Test::More;
+
+$rc = plan qw(no_plan);
+diag("Returned: " . sprintf("%d", $rc));
+
+$rc = ok(1);
+diag("Returned: $rc");
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/no_plan/test.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/no_plan/test.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/no_plan/test.t	(revision 22254)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/not-enough-tests/.cvsignore
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/not-enough-tests/.cvsignore	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/not-enough-tests/.cvsignore	(revision 22254)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/not-enough-tests/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/not-enough-tests/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/not-enough-tests/Makefile.am	(revision 22254)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/not-enough-tests/test.c
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/not-enough-tests/test.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/not-enough-tests/test.c	(revision 22254)
@@ -0,0 +1,49 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+	unsigned int rc = 0;
+
+	rc = plan_tests(1);
+	diag("Returned: %d", rc);
+
+	rc = ok(1, NULL);
+	diag("Returned: %d", rc);
+
+	rc = ok(1, NULL);
+	diag("Returned: %d", rc);
+
+	rc = ok(1, NULL);
+	diag("Returned: %d", rc);
+
+	return exit_status();
+}
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/not-enough-tests/test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/not-enough-tests/test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/not-enough-tests/test.pl	(revision 22254)
@@ -0,0 +1,20 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 1;
+diag("Returned: " . sprintf("%d", $rc));
+
+$rc = ok(1);
+diag("Returned: $rc");
+
+$rc = ok(1);
+diag("Returned: $rc");
+
+$rc = ok(1);
+diag("Returned: $rc");
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/not-enough-tests/test.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/not-enough-tests/test.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/not-enough-tests/test.t	(revision 22254)
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	# comment this out until we're exit-code compatible with Test::More
+	#retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/sane/.cvsignore
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/sane/.cvsignore	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/sane/.cvsignore	(revision 22254)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/sane/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/sane/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/sane/Makefile.am	(revision 22254)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/sane/test.c
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/sane/test.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/sane/test.c	(revision 22254)
@@ -0,0 +1,43 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+	unsigned int rc = 0;
+
+	rc = plan_tests(1);
+	diag("Returned: %d", rc);
+
+	rc = ok(1, NULL);
+	diag("Returned: %d", rc);
+
+	return exit_status();
+}
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/sane/test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/sane/test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/sane/test.pl	(revision 22254)
@@ -0,0 +1,14 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 1;
+diag("Returned: " . sprintf("%d", $rc));
+
+$rc = ok(1);
+diag("Returned: $rc");
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/sane/test.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/sane/test.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/sane/test.t	(revision 22254)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/skip_all/.cvsignore
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/skip_all/.cvsignore	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/skip_all/.cvsignore	(revision 22254)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/skip_all/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/skip_all/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/skip_all/Makefile.am	(revision 22254)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/skip_all/plan.c
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/skip_all/plan.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/skip_all/plan.c	(revision 22254)
@@ -0,0 +1,39 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "tap.h"
+
+/* Run pre-defined tests on the test library to make sure that the basic
+   functionality works, and it can be used to test itself afterwards */
+
+int
+main(int argc, char *argv[])
+{
+	plan_skip_all("No good reason");
+}
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/skip_all/test.c
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/skip_all/test.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/skip_all/test.c	(revision 22254)
@@ -0,0 +1,38 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+	unsigned int rc = 0;
+
+	rc = plan_skip_all("No good reason");
+	diag("Returned: %d", rc);
+
+	return exit_status();
+}
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/skip_all/test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/skip_all/test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/skip_all/test.pl	(revision 22254)
@@ -0,0 +1,11 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan skip_all => "No good reason";
+diag("Returned: " . sprintf("%d", $rc));
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/skip_all/test.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/skip_all/test.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/skip_all/test.t	(revision 22254)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/too-many-plans/.cvsignore
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/too-many-plans/.cvsignore	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/too-many-plans/.cvsignore	(revision 22254)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/too-many-plans/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/too-many-plans/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/too-many-plans/Makefile.am	(revision 22254)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/too-many-plans/test.c
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/too-many-plans/test.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/too-many-plans/test.c	(revision 22254)
@@ -0,0 +1,49 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+	unsigned int rc = 0;
+
+	rc = plan_tests(1);
+	diag("Returned: %d", rc);
+
+	rc = ok(1, NULL);
+	diag("Returned: %d", rc);
+
+	rc = plan_tests(1);
+	diag("Returned: %d", rc);
+
+	rc = ok(0, NULL);
+	diag("Returned: %d", rc);
+
+	return exit_status();
+}
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/too-many-plans/test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/too-many-plans/test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/too-many-plans/test.pl	(revision 22254)
@@ -0,0 +1,20 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 1;
+diag("Returned: " . sprintf("%d", $rc));
+
+$rc = ok(1);
+diag("Returned: $rc");
+
+$rc = plan tests => 1;
+diag("Returned: $rc");
+
+$rc = ok(0);
+diag("Returned: $rc");
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/too-many-plans/test.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/too-many-plans/test.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/too-many-plans/test.t	(revision 22254)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/too-many-tests/.cvsignore
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/too-many-tests/.cvsignore	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/too-many-tests/.cvsignore	(revision 22254)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/too-many-tests/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/too-many-tests/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/too-many-tests/Makefile.am	(revision 22254)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/too-many-tests/test.c
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/too-many-tests/test.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/too-many-tests/test.c	(revision 22254)
@@ -0,0 +1,46 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+	unsigned int rc = 0;
+
+	rc = plan_tests(5);
+	diag("Returned: %d", rc);
+
+	rc = ok(1, NULL);
+	diag("Returned: %d", rc);
+
+	rc = ok(0, NULL);
+	diag("Returned: %d", rc);
+
+	return exit_status();
+}
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/too-many-tests/test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/too-many-tests/test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/too-many-tests/test.pl	(revision 22254)
@@ -0,0 +1,17 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 5;
+diag("Returned: " . sprintf("%d", $rc));
+
+$rc = ok(1);
+diag("Returned: $rc");
+
+$rc = ok(0);
+diag("Returned: $rc");
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/too-many-tests/test.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/too-many-tests/test.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/plan/too-many-tests/test.t	(revision 22254)
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+    # we're not exit-status compatible with Test::More yet
+	#retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/skip/.cvsignore
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/skip/.cvsignore	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/skip/.cvsignore	(revision 22254)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/skip/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/skip/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/skip/Makefile.am	(revision 22254)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/skip/test.c
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/skip/test.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/skip/test.c	(revision 22254)
@@ -0,0 +1,69 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+	unsigned int rc = 0;
+	unsigned int side_effect = 0;
+
+	rc = plan_tests(4);
+	diag("Returned: %d", rc);
+
+	rc = ok(1 == 1, "1 equals 1");	/* Should always work */
+	diag("Returned: %d", rc);
+
+	do {
+		if(1) {
+			rc = skip(1, "Testing skipping");
+			continue;
+		}
+		
+		side_effect++;
+
+		ok(side_effect == 1, "side_effect checked out");
+
+	} while(0);
+
+	diag("Returned: %d", rc);
+
+	skip_start(1 == 1, 1, "Testing skipping #2");
+
+	side_effect++;
+	rc = ok(side_effect == 1, "side_effect checked out");
+	diag("Returned: %d", rc);
+
+	skip_end();
+
+	rc = ok(side_effect == 0, "side_effect is %d", side_effect);
+	diag("Returned: %d", rc);
+
+	return exit_status();
+}
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/skip/test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/skip/test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/skip/test.pl	(revision 22254)
@@ -0,0 +1,40 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 4;
+diag("Returned: " . sprintf("%d", $rc));
+
+my $side_effect = 0;		# Check whether skipping has side effects
+
+$rc = ok(1 == 1, '1 equals 1');	# Test ok() passes when it should
+diag("Returned: $rc");
+
+# Start skipping
+SKIP: {
+	$rc = skip "Testing skipping", 1;
+
+	$side_effect++;
+
+	$rc = ok($side_effect == 1, '$side_effect checked out');
+}
+
+diag("Returned: $rc");
+
+SKIP: {
+	$rc = skip "Testing skipping #2", 1;
+	diag("Returned: $rc");
+
+	$side_effect++;
+
+	$rc = ok($side_effect == 1, '$side_effect checked out');
+	diag("Returned: $rc");
+}
+
+$rc = ok($side_effect == 0, "side_effect is $side_effect");
+diag("Returned: $rc");
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/skip/test.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/skip/test.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/skip/test.t	(revision 22254)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/todo/.cvsignore
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/todo/.cvsignore	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/todo/.cvsignore	(revision 22254)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/todo/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/todo/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/todo/Makefile.am	(revision 22254)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/todo/test.c
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/todo/test.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/todo/test.c	(revision 22254)
@@ -0,0 +1,68 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+	unsigned int rc = 0;
+	unsigned int side_effect = 0;
+
+	rc = plan_tests(5);
+	diag("Returned: %d", rc);
+
+	rc = ok(1 == 1, "1 equals 1");	/* Should always work */
+	diag("Returned: %d", rc);
+
+	todo_start("For testing purposes");
+
+	side_effect++;
+
+	/* This test should fail */
+	rc = ok(side_effect == 0, "side_effect checked out");
+	diag("Returned: %d", rc);
+
+	/* This test should unexpectedly succeed */
+	rc = ok(side_effect == 1, "side_effect checked out");
+	diag("Returned: %d", rc);
+
+	todo_end();
+
+	todo_start("Testing printf() %s in todo_start()", "expansion");
+
+	rc = ok(0, "dummy test");
+	diag("Returned: %d", rc);
+
+	todo_end();
+
+	rc = ok(side_effect == 1, "side_effect is %d", side_effect);
+	diag("Returned: %d", rc);
+
+	return exit_status();
+}
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/todo/test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/todo/test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/todo/test.pl	(revision 22254)
@@ -0,0 +1,41 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 5;
+diag("Returned: " . sprintf("%d", $rc));
+
+my $side_effect = 0;		# Check whether TODO has side effects
+
+$rc = ok(1 == 1, '1 equals 1');	# Test ok() passes when it should
+diag("Returned: $rc");
+
+# Start TODO tests
+TODO: {
+	local $TODO = 'For testing purposes';
+
+	$side_effect++;
+
+	# This test should fail
+	$rc = ok($side_effect == 0, 'side_effect checked out');
+	diag("Returned: $rc");
+
+	# This test should unexpectedly succeed
+	$rc = ok($side_effect == 1, 'side_effect checked out');
+	diag("Returned: $rc");
+}
+
+TODO: {
+	local $TODO = 'Testing printf() expansion in todo_start()';
+
+	$rc = ok(0, 'dummy test');
+	diag("Returned: $rc");
+}
+
+$rc = ok($side_effect == 1, "side_effect is $side_effect");
+diag("Returned: $rc");
Index: /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/todo/test.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/todo/test.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebclient/tests/tap/tests/todo/test.t	(revision 22254)
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+# Test:;More prints diagnostic from TODO tests on stdout
+# http://rt.cpan.org/Ticket/Display.html?id=14982
+sed '/^#/D' test.pl.out > tmp
+mv tmp test.pl.out
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/jh_branches/jhoblitt/Nebulous/nebulous.wsdl
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/nebulous.wsdl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/nebulous.wsdl	(revision 22254)
@@ -0,0 +1,96 @@
+<?xml version="1.0"?>
+
+<definitions name="idata" targetNamespace="http://pan-starrs.ifa.hawaii.edu/PS/IPP/IData/Server">
+
+    <types>
+        <schema targetNamespace="http://pan-starrs.ifa.hawaii.edu/PS/IPP/IData/Server"
+            xmlns="http://www.w3.org/1999/XMLSchema">
+
+            <element name="create_object">
+                <complexType>
+                    <sequence>
+                        <element name="key"     type="string"/>
+                        <element name="class"   type="int"/>
+                        <element name="volume"  type="string"/>
+                        <element name="comment" type="string"/>
+                    </sequence>
+                </complexType>
+            </element>
+
+            <element name="create_objectResponse">
+                <complexType>
+                    <all>
+                        <element name="uri" type="string"/>
+                    </all>
+                </complexType>
+            </element>
+
+            <element name="create_objectFault">
+                <complexType>
+                    <all>
+                        <element name="errorMessage" type="string"/>
+                    </all>
+                </complexType>
+            </element>
+
+        </schema>
+    </types>
+
+    <message name="create_objectRequest">
+        <part name="body" element="esxsd:create_object"/>
+    </message>
+
+    <message name="create_objectResponse">
+        <part name="body" element="esxsd:create_objectResponse"/>
+    </message>
+
+    <portType name="create_objectPortType">
+        <operation name="create_object">
+            <input message ="es:create_objectRequest"/>
+            <output message="es:create_objectResponse"/>
+            <fault message ="es:create_objectFault"/>
+        </operation>
+    </portType>
+
+    <binding name="idataSoapBinding"
+        type="es:GetidatarPortType">
+
+        <soap:binding style="document"
+            transport="http://schemas.xmlsoap.org/soap/http"/>
+
+        <operation name="Getidata">
+            <soap:operation
+                soapAction="http://www.snowboard-info.com/EndorsementSearch"/>
+            <input>
+                <soap:body use="literal"
+                    namespace="http://schemas.snowboard-info.com/EndorsementSearch.xsd"/>
+            </input>
+
+            <output>
+                <soap:body use="literal"
+                    namespace="http://schemas.snowboard-info.com/EndorsementSearch.xsd"/>
+            </output>
+
+            <fault>
+                <soap:body use="literal"
+                    namespace="http://schemas.snowboard-info.com/EndorsementSearch.xsd"/>
+            </fault>
+        </operation>
+    </binding>
+
+
+  <service name="EndorsementSearchService">
+
+    <documentation>snowboarding-info.com Endorsement Service</documentation> 
+
+    <port name="GetEndorsingBoarderPort"
+          binding="es:EndorsementSearchSoapBinding">
+
+      <soap:address
+location="http://www.snowboard-info.com/EndorsementSearch"/>
+
+    </port>
+
+  </service>
+
+</definitions>
Index: /branches/jh_branches/jhoblitt/Nebulous/scripts/bench_test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/scripts/bench_test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/scripts/bench_test.pl	(revision 22254)
@@ -0,0 +1,28 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use lib "./lib";
+use Benchmark qw( cmpthese );
+
+use PS::IPP::IData::Client;
+
+my $idata = PS::IPP::IData::Client->new(
+    proxy   => 'http://localhost:80/idata'
+);
+
+my $key = "foobarbazbongbungbutufu";
+cmpthese( 10, {
+        'Image Server' => sub {
+            {
+                my $fh = $idata->create( $key );
+                close $fh;
+                $idata->unlock( $key, 'write' );
+            }
+
+            $idata->delete( $key );
+        },
+    },
+    'all',
+);
Index: /branches/jh_branches/jhoblitt/Nebulous/scripts/imageserver.pl
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/scripts/imageserver.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/scripts/imageserver.pl	(revision 22254)
@@ -0,0 +1,14 @@
+#!/usr/bin/perl -w
+
+# Copyright (C) 2004  Joshua Hoblitt
+#
+# $Id
+ 
+use strict;
+
+use SOAP::Transport::HTTP;
+use PS::IPP::IData::Server;
+   
+SOAP::Transport::HTTP::CGI
+    -> dispatch_to( 'PS::IPP::IData::Server' )
+    -> handle;
Index: /branches/jh_branches/jhoblitt/Nebulous/scripts/ptest.pl
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/scripts/ptest.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/scripts/ptest.pl	(revision 22254)
@@ -0,0 +1,51 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use lib "./lib";
+
+use PS::IPP::IData::Client;
+
+my $idata = PS::IPP::IData::Client->new(
+    uri     => 'http://localhost:80/PS/IPP/IData/Server',
+    proxy   => 'http://localhost:80/idata'
+);
+
+my $key = shift;
+
+my $kids = shift || 1;
+
+foreach my $id ( 1..$kids ) {
+    my $pid = fork;
+
+    unless ( $pid )  {
+        my $fname = "${key}_$id";
+        my $fh = $idata->create( $fname );
+        die "can't create file $fname" unless $fh;
+
+        print $fh "fooby\n";
+
+        close $fh;
+        $idata->unlock( $fname, 'write' );
+
+        $fh = $idata->open( $fname, 'read' ) or die "can't open file";
+        close $fh;
+        $idata->unlock( $fname, 'read' );
+
+        $idata->replicate( $fname );
+        $idata->cull( $fname );
+        $idata->find( $fname );
+        $idata->copy( $fname, $fname . "_copy" );
+        $idata->move( $fname, $fname . "_move" );
+        $idata->delete( $fname . "_copy" );
+        $idata->delete( $fname . "_move" );
+
+        exit 0;
+    }
+}
+
+while ( $kids ) {
+    wait();
+    $kids --;
+}
Index: /branches/jh_branches/jhoblitt/Nebulous/scripts/stest.pl
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/scripts/stest.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/scripts/stest.pl	(revision 22254)
@@ -0,0 +1,33 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use lib "./lib";
+
+use PS::IPP::IData::Client; # trace => 'debug';
+
+my $idata = PS::IPP::IData::Client->new(
+    proxy   => 'http://localhost:80/idata'
+);
+
+my $fname = shift;
+
+#my $fh = $idata->create( $fname );
+#$idata->unlock( $fname, 'write' );
+
+$idata->create( $fname, 1 );
+$idata->unlock( $fname, 'write' );
+$idata->delete( $fname );
+
+__END__
+
+{
+    my $fh = $pd->create( $key );
+    die "can't create file $fname" unless $fh;
+    print $fh "fooby\n";
+    close $fh;
+    #$idata->unlock( $fname, 'write' );
+    my $filesize =  (stat( $key ))[7];
+    print "created file $key, ${filesize}bytes\n";
+}
Index: /branches/jh_branches/jhoblitt/Nebulous/t/01_load.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/t/01_load.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/t/01_load.t	(revision 22254)
@@ -0,0 +1,19 @@
+# -*- perl -*-
+
+# t/001_load.t - check module loading and create testing directory
+
+use strict;
+use warnings;
+
+use lib qw( ./t ./lib );
+
+use Test::More tests => 8;
+
+BEGIN { use_ok( 'PS::IPP::IData::Client' ); }
+BEGIN { use_ok( 'PS::IPP::IData::Client::Log' ); }
+BEGIN { use_ok( 'PS::IPP::IData::Client::HTTP' ); }
+BEGIN { use_ok( 'PS::IPP::IData::Util' ); }
+BEGIN { use_ok( 'PS::IPP::IData::Server' ); }
+BEGIN { use_ok( 'PS::IPP::IData::Server::Apache' ); }
+BEGIN { use_ok( 'PS::IPP::IData::Server::SQL' ); }
+BEGIN { use_ok( 'PS::IPP::IData::Server::Log' ); }
Index: /branches/jh_branches/jhoblitt/Nebulous/t/02_server_setup.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/t/02_server_setup.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/t/02_server_setup.t	(revision 22254)
@@ -0,0 +1,35 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 2;
+
+use lib qw( ./t ./lib );
+
+use PS::IPP::IData::Server;
+use Test::IData;
+
+Test::IData->setup;
+
+ok(
+    PS::IPP::IData::Server->setup(
+        "DBI:mysql:database=test:host=localhost",
+        "test",
+        "",
+    ),
+    "connect",
+);
+
+Test::IData->setup;
+
+eval {
+    PS::IPP::IData::Server->setup(
+        "DBI:mysql:database=foobar:host=localhost",
+        "baz",
+        "boo",
+    );
+};
+like( $@, qr/DBI connect.*? failed/, "bad dsn/user/pass" );
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous/t/03_server_create_object.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/t/03_server_create_object.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/t/03_server_create_object.t	(revision 22254)
@@ -0,0 +1,84 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 11;
+
+use lib qw( ./t ./lib );
+
+use PS::IPP::IData::Server;
+use PS::IPP::IData::Util qw( :standard );
+use Test::IData;
+
+my $idata = "PS::IPP::IData::Server";
+
+$idata->setup(
+    "DBI:mysql:database=test:host=localhost",
+    "test",
+    "",
+);
+
+Test::IData->setup;
+
+{
+    # key
+    my $uri = $idata->create_object( "foo" );
+
+    ok( -e _get_file_path( $uri ), "file exists" );
+    like( $uri, qr/^file:\//, "valid URI" );
+}
+
+Test::IData->setup;
+
+{
+    # key, class
+    my $uri = $idata->create_object( "foo", 0 );
+
+    ok( -e _get_file_path( $uri ), "file exists" );
+    like( $uri, qr/^file:\//, "valid URI" );
+}
+
+Test::IData->setup;
+
+{
+    # key, class, volume
+    my $uri = $idata->create_object( "foo", 0, "node01" );
+
+    ok( -e _get_file_path( $uri ), "file exists" );
+    like( $uri, qr/^file:\//, "valid URI" );
+}
+
+Test::IData->setup;
+
+{
+    # key, class, volume, comment
+    my $uri = $idata->create_object( "foo", 0, "node01", "this is foo" );
+
+    ok( -e _get_file_path( $uri ), "file exists" );
+    like( $uri, qr/^file:\//, "valid URI" );
+}
+
+Test::IData->setup;
+
+eval {
+    $idata->create_object( "foo" );
+    $idata->create_object( "foo" );
+};
+like( $@, qr/Duplicate entry/, "object already exists" );
+
+Test::IData->setup;
+
+eval {
+    $idata->create_object();
+};
+like( $@, qr/2 - 4 were expected/, "no params" );
+
+Test::IData->setup;
+
+eval {
+    $idata->create_object( 1, 2, 3, 4, 5 );
+};
+like( $@, qr/2 - 4 were expected/, "too many params" );
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous/t/04_server_replicate_object.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/t/04_server_replicate_object.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/t/04_server_replicate_object.t	(revision 22254)
@@ -0,0 +1,79 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 11;
+
+use lib qw( ./t ./lib );
+
+use PS::IPP::IData::Server;
+use PS::IPP::IData::Util qw( :standard );
+use Test::IData;
+
+my $idata = "PS::IPP::IData::Server";
+
+$idata->setup(
+    "DBI:mysql:database=test:host=localhost",
+    "test",
+    "",
+);
+
+Test::IData->setup;
+
+{
+    # key
+    $idata->create_object( "foo" );
+    my $uri = $idata->replicate_object( "foo" );
+
+    ok( -e _get_file_path( $uri ), "file exists" );
+    like( $uri, qr/^file:\//, "valid URI" );
+}
+
+Test::IData->setup;
+
+{
+    # key, $volume
+    $idata->create_object( "foo" );
+    my $uri = $idata->replicate_object( "foo", "node01" );
+
+    ok( -e _get_file_path( $uri ), "file exists" );
+    like( $uri, qr/^file:\//, "valid URI" );
+}
+
+Test::IData->setup;
+
+{
+    # key, $volume
+    $idata->create_object( "foo" );
+    my $uri1 = $idata->replicate_object( "foo", "node01" );
+    my $uri2 = $idata->replicate_object( "foo", "node02" );
+
+    ok( -e _get_file_path( $uri1 ), "file exists" );
+    ok( -e _get_file_path( $uri2 ), "file exists" );
+    like( $uri1, qr/^file:\//, "valid URI" );
+    like( $uri2, qr/^file:\//, "valid URI" );
+}
+
+Test::IData->setup;
+
+eval {
+    $idata->replicate_object( "foo" );
+};
+like( $@, qr/storage object does not exist/, "storage object does not exist" );
+
+Test::IData->setup;
+
+eval {
+    $idata->replicate_object();
+};
+like( $@, qr/1 - 2 were expected/, "no params" );
+
+Test::IData->setup;
+
+eval {
+    $idata->replicate_object( 1, 2, 3 );
+};
+like( $@, qr/1 - 2 were expected/, "too many params" );
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous/t/05_server_lock_object.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/t/05_server_lock_object.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/t/05_server_lock_object.t	(revision 22254)
@@ -0,0 +1,119 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 13;
+
+use lib qw( ./t ./lib );
+
+use PS::IPP::IData::Server;
+use PS::IPP::IData::Util qw( :standard );
+use Test::IData;
+
+my $idata = "PS::IPP::IData::Server";
+
+$idata->setup(
+    "DBI:mysql:database=test:host=localhost",
+    "test",
+    "",
+);
+
+Test::IData->setup;
+
+{
+    $idata->create_object( "foo" );
+
+    ok( $idata->lock_object( "foo", "read" ), "read lock" );
+}
+
+Test::IData->setup;
+
+{
+    $idata->create_object( "foo" );
+
+    ok( $idata->lock_object( "foo", "read" ), "read lock" );
+    ok( $idata->lock_object( "foo", "read" ), "read lock" );
+}
+
+Test::IData->setup;
+
+{
+    $idata->create_object( "foo" );
+
+    ok( $idata->lock_object( "foo", "write" ), "write lock" );
+}
+
+Test::IData->setup;
+
+eval {
+    $idata->lock_object( "foo", "read" );
+};
+like( $@, qr/storage object does not exist/, "storage object does not exist" );
+
+Test::IData->setup;
+
+eval {
+    $idata->lock_object( "foo", "write" );
+};
+like( $@, qr/storage object does not exist/, "storage object does not exist" );
+
+Test::IData->setup;
+
+eval {
+    $idata->create_object( "foo" );
+
+    $idata->lock_object( "foo", "write" );
+    $idata->lock_object( "foo", "write" );
+};
+like( $@, qr/can not write lock twice/, "can not write lock twice" );
+
+Test::IData->setup;
+
+eval {
+    $idata->create_object( "foo" );
+
+    $idata->lock_object( "foo", "read" );
+    $idata->lock_object( "foo", "write" );
+};
+like( $@, qr/can not write lock after read lock/, "can not write lock after read lock" );
+
+Test::IData->setup;
+
+eval {
+    $idata->create_object( "foo" );
+
+    $idata->lock_object( "foo", "write" );
+    $idata->lock_object( "foo", "read" );
+};
+like( $@, qr/can not read lock after write lock/, "can not read lock after write lock" );
+
+Test::IData->setup;
+
+eval {
+    $idata->lock_object();
+};
+like( $@, qr/2 were expected/, "no params" );
+
+Test::IData->setup;
+
+eval {
+    $idata->lock_object( "foo" );
+};
+like( $@, qr/2 were expected/, "not enough params" );
+
+Test::IData->setup;
+
+eval {
+    $idata->lock_object( "foo", "both" );
+};
+like( $@, qr/is read or write/, "not read or write" );
+
+Test::IData->setup;
+
+eval {
+    $idata->lock_object( "foo", 'read', 3 );
+};
+like( $@, qr/2 were expected/, "too many params" );
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous/t/06_server_unlock_object.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/t/06_server_unlock_object.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/t/06_server_unlock_object.t	(revision 22254)
@@ -0,0 +1,134 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 14;
+
+use lib qw( ./t ./lib );
+
+use PS::IPP::IData::Server;
+use PS::IPP::IData::Util qw( :standard );
+use Test::IData;
+
+my $idata = "PS::IPP::IData::Server";
+
+$idata->setup(
+    "DBI:mysql:database=test:host=localhost",
+    "test",
+    "",
+);
+
+Test::IData->setup;
+
+{
+    $idata->create_object( "foo" );
+
+    $idata->lock_object( "foo", "read" );
+
+    ok( $idata->unlock_object( "foo", "read" ), "read unlock" );
+}
+
+Test::IData->setup;
+
+{
+    $idata->create_object( "foo" );
+
+    $idata->lock_object( "foo", "read" );
+    $idata->lock_object( "foo", "read" );
+
+    ok( $idata->unlock_object( "foo", "read" ), "read unlock" );
+    ok( $idata->unlock_object( "foo", "read" ), "read unlock" );
+}
+
+Test::IData->setup;
+
+{
+    $idata->create_object( "foo" );
+
+    $idata->lock_object( "foo", "write" );
+
+    ok( $idata->unlock_object( "foo", "write" ), "write unlock" );
+}
+
+Test::IData->setup;
+
+eval {
+    $idata->unlock_object( "foo", "read" );
+};
+like( $@, qr/storage object does not exist/, "storage object does not exist" );
+
+Test::IData->setup;
+
+eval {
+    $idata->unlock_object( "foo", "write" );
+};
+like( $@, qr/storage object does not exist/, "storage object does not exist" );
+
+Test::IData->setup;
+
+eval {
+    $idata->create_object( "foo" );
+
+    $idata->unlock_object( "foo", "read" );
+};
+like( $@, qr/can not remove non-existant read lock/, "no lock set" );
+
+Test::IData->setup;
+
+eval {
+    $idata->create_object( "foo" );
+
+    $idata->unlock_object( "foo", "write" );
+};
+like( $@, qr/can not remove non-existant write lock/, "no lock set" );
+
+Test::IData->setup;
+
+eval {
+    $idata->create_object( "foo" );
+    $idata->lock_object( "foo", "write" );
+
+    $idata->unlock_object( "foo", "read" );
+};
+like( $@, qr/can not have a read lock under a write lock/, "read unlock under write lock" );
+
+Test::IData->setup;
+
+eval {
+    $idata->create_object( "foo" );
+    $idata->lock_object( "foo", "read" );
+
+    $idata->unlock_object( "foo", "write" );
+};
+like( $@, qr/can not have a write lock under a read lock/, "write unlock under read lock" );
+
+Test::IData->setup;
+
+eval {
+    $idata->unlock_object();
+};
+like( $@, qr/2 were expected/, "no params" );
+
+Test::IData->setup;
+
+eval {
+    $idata->unlock_object( "foo" );
+};
+like( $@, qr/2 were expected/, "not enough params" );
+
+Test::IData->setup;
+
+eval {
+    $idata->unlock_object( "foo", "both" );
+};
+like( $@, qr/is read or write/, "not read or write" );
+
+Test::IData->setup;
+
+eval {
+    $idata->unlock_object( "foo", 'read', 3 );
+};
+like( $@, qr/2 were expected/, "too many params" );
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous/t/07_server_find_instances.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/t/07_server_find_instances.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/t/07_server_find_instances.t	(revision 22254)
@@ -0,0 +1,90 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 7;
+
+use lib qw( ./t ./lib );
+
+use PS::IPP::IData::Server;
+use PS::IPP::IData::Util qw( :standard );
+use Test::IData;
+
+my $idata = "PS::IPP::IData::Server";
+
+$idata->setup(
+    "DBI:mysql:database=test:host=localhost",
+    "test",
+    "",
+);
+
+Test::IData->setup;
+
+{
+    # key
+    my $uri = $idata->create_object( "foo" );
+
+    my $locations = $idata->find_instances( "foo" );
+
+    is( $uri, $locations->[0], "URIs match" );
+}
+
+Test::IData->setup;
+
+{
+    # key
+    my $uri1 = $idata->create_object( "foo" );
+    my $uri2 = $idata->replicate_object( "foo" );
+
+    my $locations = $idata->find_instances( "foo" );
+
+    ok( eq_set( [$uri1, $uri2], $locations ), "URIs match" );
+}
+
+Test::IData->setup;
+
+{
+    # key, volume
+    my $uri = $idata->create_object( "foo" );
+
+    my $locations = $idata->find_instances( "foo", "node01" );
+
+    is( $uri, $locations->[0], "URIs match" );
+}
+
+Test::IData->setup;
+
+{
+    # key
+    my $uri1 = $idata->create_object( "foo" );
+    my $uri2 = $idata->replicate_object( "foo" );
+
+    my $locations = $idata->find_instances( "foo", "node01" );
+
+    ok( eq_set( [$uri1, $uri2], $locations ), "URIs match" );
+}
+
+
+Test::IData->setup;
+
+eval {
+    $idata->find_instances( "foo" );
+};
+like( $@, qr/no instances found/, "storage object does not exist" );
+
+Test::IData->setup;
+
+eval {
+    $idata->find_instances();
+};
+like( $@, qr/1 - 2 were expected/, "no params" );
+
+Test::IData->setup;
+
+eval {
+    $idata->find_instances( "foo", 'read', 3 );
+};
+like( $@, qr/1 - 2 were expected/, "too many params" );
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous/t/08_server_delete_instance.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/t/08_server_delete_instance.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/t/08_server_delete_instance.t	(revision 22254)
@@ -0,0 +1,71 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 8;
+
+use lib qw( ./t ./lib );
+
+use PS::IPP::IData::Server;
+use PS::IPP::IData::Util qw( :standard );
+use Test::IData;
+
+my $idata = "PS::IPP::IData::Server";
+
+$idata->setup(
+    "DBI:mysql:database=test:host=localhost",
+    "test",
+    "",
+);
+
+Test::IData->setup;
+
+{
+    my $uri = $idata->create_object( "foo" );
+
+    ok( $idata->delete_instance( $uri ), "delete instance" );
+}
+
+Test::IData->setup;
+
+{
+    my $uri1 = $idata->create_object( "foo" );
+    my $uri2 = $idata->replicate_object( "foo" );
+
+    ok( $idata->delete_instance( $uri1 ), "delete instance" );
+
+    my $locations = $idata->find_instances( "foo" );
+
+    is( $locations->[0], $uri2, "instance remains" );
+
+    ok( $idata->delete_instance( $uri2 ), "delete instance" );
+
+    eval {
+        $idata->find_instances( "foo" );
+    };
+    like( $@, qr/no instances found/, "storage object was deleted" );
+}
+
+Test::IData->setup;
+
+eval {
+    $idata->delete_instance( "file:/foo" );
+};
+like( $@, qr/no instance is associated with uri/, "uri does not exist" );
+
+Test::IData->setup;
+
+eval {
+    $idata->delete_instance();
+};
+like( $@, qr/1 was expected/, "no params" );
+
+Test::IData->setup;
+
+eval {
+    $idata->delete_instance( "foo", 2 );
+};
+like( $@, qr/1 was expected/, "too many params" );
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous/t/09_server_stat_object.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/t/09_server_stat_object.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/t/09_server_stat_object.t	(revision 22254)
@@ -0,0 +1,70 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 12;
+
+use lib qw( ./t ./lib );
+
+use PS::IPP::IData::Server;
+use PS::IPP::IData::Util qw( :standard );
+use Test::IData;
+
+my $idata = "PS::IPP::IData::Server";
+
+$idata->setup(
+    "DBI:mysql:database=test:host=localhost",
+    "test",
+    "",
+);
+
+Test::IData->setup;
+
+{
+    my $uri = $idata->create_object( "foo" );
+
+    my $info = $idata->stat_object( "foo" );
+
+    is( scalar @$info, 7, "number of columns" );
+}
+
+Test::IData->setup;
+
+{
+    my $uri = $idata->create_object( "foo", 9, "node01", "foobar" );
+
+    my $info = $idata->stat_object( "foo" );
+
+    is( scalar @$info, 7,                       "number of columns" );
+    is( @$info[0], 1,                           "so_id" );
+    is( @$info[1], "foo",                       "ext_id" );
+    is( @$info[2], 9,                           "class_id" );
+    is( @$info[3], "foobar",                    "comment" );
+    is( @$info[4], 0,                           "read lock" );
+    is( @$info[5], undef,                       "write lock" );
+    like( @$info[6], qr/....-..-.. ..:..:../,   "epoch" );
+}
+
+Test::IData->setup;
+
+eval {
+    $idata->stat_object( "foo" );
+};
+like( $@, qr/no storage object found/, "object does not exist" );
+
+Test::IData->setup;
+
+eval {
+    $idata->stat_object();
+};
+like( $@, qr/1 was expected/, "no params" );
+
+Test::IData->setup;
+
+eval {
+    $idata->stat_object( "foo", 2 );
+};
+like( $@, qr/1 was expected/, "too many params" );
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous/t/10_client_new.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/t/10_client_new.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/t/10_client_new.t	(revision 22254)
@@ -0,0 +1,56 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Apache::Test qw( -withtestmore );
+
+plan tests => 4;
+
+use lib qw( ./t ./lib );
+
+use PS::IPP::IData::Client;
+use PS::IPP::IData::Util qw( :standard );
+use Test::IData;
+
+my $hostport = Apache::Test->config->{ 'hostport' };
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    isa_ok( $idata, "PS::IPP::IData::Client" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+        uri   => "http://example.com/PS/IPP/IData/Client",
+    );
+
+    isa_ok( $idata, "PS::IPP::IData::Client" );
+}
+
+Test::IData->setup;
+
+eval {
+    PS::IPP::IData::Client->new;
+};
+like( $@, qr/Mandatory parameter/, "no proxy" );
+
+Test::IData->setup;
+
+eval {
+    PS::IPP::IData::Client->new(
+        proxy => "foo",
+        dog => "do"
+    );
+};
+like( $@, qr/not listed in the validation options/, "bad param" );
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous/t/11_client_create.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/t/11_client_create.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/t/11_client_create.t	(revision 22254)
@@ -0,0 +1,103 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Apache::Test qw( -withtestmore );
+
+plan tests => 7;
+
+use lib qw( ./t ./lib );
+
+use PS::IPP::IData::Client;
+use PS::IPP::IData::Util qw( :standard );
+use Test::IData;
+
+my $hostport = Apache::Test->config->{ 'hostport' };
+
+Test::IData->setup;
+
+{
+    # key
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    my $fh = $idata->create( "foo" );
+
+    is( ref $fh, 'GLOB', "good filehandle" );
+}
+
+Test::IData->setup;
+
+{
+    # key, class
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    my $fh = $idata->create( "foo", 0 );
+
+    is( ref $fh, 'GLOB', "good filehandle" );
+}
+
+Test::IData->setup;
+
+{
+    # key, class, volume
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    my $fh = $idata->create( "foo", 0, "node01" );
+
+    is( ref $fh, 'GLOB', "good filehandle" );
+}
+
+Test::IData->setup;
+
+{
+    # key, class, volume, comment
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    my $fh = $idata->create( "foo", 0, "node01", "this is foo" );
+
+    is( ref $fh, 'GLOB', "good filehandle" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    $idata->create( "foo" );
+    is( $idata->create( "foo" ), undef, "object already exists" );
+}
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    $idata->create();
+};
+like( $@, qr/2 - 4 were expected/, "no params" );
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    $idata->create( 1, 2, 3, 4, 5 );
+};
+like( $@, qr/2 - 4 were expected/, "too many params" );
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous/t/12_client_replicate.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/t/12_client_replicate.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/t/12_client_replicate.t	(revision 22254)
@@ -0,0 +1,97 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Apache::Test qw( -withtestmore );
+
+plan tests => 11;
+
+use lib qw( ./t ./lib );
+
+use PS::IPP::IData::Client;
+use PS::IPP::IData::Util qw( :standard );
+use Test::IData;
+
+my $hostport = Apache::Test->config->{ 'hostport' };
+
+Test::IData->setup;
+
+{
+    # key
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    my $uri = $idata->replicate( "foo" );
+
+    ok( $uri, "good replication" );
+    ok( -e _get_file_path( $uri ), "file exists" );
+}
+
+Test::IData->setup;
+
+{
+    # key, $volume
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    my $uri = $idata->replicate( "foo", "node01" );
+
+    ok( $uri, "good replication" );
+    ok( -e _get_file_path( $uri ), "file exists" );
+}
+
+Test::IData->setup;
+
+{
+    # key, $volume
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    my $uri1 = $idata->replicate( "foo", "node01" );
+    my $uri2 = $idata->replicate( "foo", "node02" );
+
+    ok( $uri1, "good replication" );
+    ok( $uri2, "good replication" );
+    ok( -e _get_file_path( $uri1 ), "file exists" );
+    ok( -e _get_file_path( $uri2 ), "file exists" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    my $uri = $idata->replicate( "foo" );
+
+    is( $uri, undef, "storage object does not exist" );
+}
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->replicate();
+};
+like( $@, qr/1 - 2 were expected/, "no params" );
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->replicate( 1, 2, 3 );
+};
+like( $@, qr/1 - 2 were expected/, "too many params" );
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous/t/13_client_cull.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/t/13_client_cull.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/t/13_client_cull.t	(revision 22254)
@@ -0,0 +1,127 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Apache::Test qw( -withtestmore );
+
+plan tests => 15;
+
+use lib qw( ./t ./lib );
+
+use PS::IPP::IData::Client;
+use PS::IPP::IData::Util qw( :standard );
+use Test::IData;
+
+my $hostport = Apache::Test->config->{ 'hostport' };
+
+Test::IData->setup;
+
+{
+    # key
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+    $idata->replicate( "foo" );
+
+    my $uri = $idata->cull( "foo" );
+
+    ok( $uri, "good cull" );
+    ok( ! -e _get_file_path( $uri ), "file doesn't exist" );
+}
+
+Test::IData->setup;
+
+SKIP: {
+    skip "unsure if this should be part of the API", 2;
+
+    # key, $volume
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+    $idata->replicate( "foo", "node01" );
+
+    my $uri = $idata->cull( "foo", "node01" );
+
+    ok( $uri, "good cull" );
+    ok( ! -e _get_file_path( $uri ), "file exists" );
+}
+
+Test::IData->setup;
+
+{
+    # key
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    $idata->replicate( "foo", "node01" );
+    $idata->replicate( "foo", "node02" );
+
+    my $uri1 = $idata->cull( "foo" );
+    my $uri2 = $idata->cull( "foo" );
+
+    ok( $uri1, "good cull" );
+    ok( $uri2, "good cull" );
+    ok( ! -e _get_file_path( $uri1 ), "file exists" );
+    ok( ! -e _get_file_path( $uri2 ), "file exists" );
+}
+
+Test::IData->setup;
+
+SKIP: {
+    skip "unsure if this should be part of the API", 4;
+
+    # key, $volume
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    $idata->replicate( "foo", "node01" );
+    $idata->replicate( "foo", "node02" );
+
+    my $uri1 = $idata->cull( "foo", "node01" );
+    my $uri2 = $idata->cull( "foo", "node02" );
+
+    ok( $uri1, "good cull" );
+    ok( $uri2, "good cull" );
+    ok( ! -e _get_file_path( $uri1 ), "file exists" );
+    ok( ! -e _get_file_path( $uri2 ), "file exists" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    my $uri = $idata->cull( "foo" );
+
+    is( $uri, undef, "storage object does not exist" );
+}
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->cull();
+};
+like( $@, qr/1 was expected/, "no params" );
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->cull( 1, 2 );
+};
+like( $@, qr/1 was expected/, "too many params" );
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous/t/14_client_lock.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/t/14_client_lock.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/t/14_client_lock.t	(revision 22254)
@@ -0,0 +1,269 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Apache::Test qw( -withtestmore );
+
+plan tests => 19;
+
+use lib qw( ./t ./lib );
+
+use PS::IPP::IData::Client;
+use PS::IPP::IData::Util qw( :standard );
+use Test::IData;
+
+my $hostport = Apache::Test->config->{ 'hostport' };
+
+Test::IData->setup;
+
+{
+    # key, type
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    ok( $idata->lock( "foo", "read" ), "read lock" );
+}
+
+Test::IData->setup;
+
+{
+    # key, type, timeout
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    ok( $idata->lock( "foo", "read", 10 ), "read lock" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    ok( $idata->lock( "foo", "read" ), "read lock" );
+    ok( $idata->lock( "foo", "read" ), "read lock" );
+}
+
+Test::IData->setup;
+
+{
+    # key, type
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    ok( $idata->lock( "foo", "write" ), "write lock" );
+}
+
+Test::IData->setup;
+
+{
+    # key, type, timeout
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    ok( $idata->lock( "foo", "write", 10 ), "write lock" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    is( $idata->lock( "foo", "read" ), undef, "storage object does not exist" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    is( $idata->lock( "foo", "write" ), undef, "storage object does not exist" );
+}
+
+Test::IData->setup;
+
+diag "testing a spinlock, this will be a litle slow...";
+
+eval {
+    local $SIG{ALRM} = sub { alarm 0; die "timeout"; };
+    alarm 8;
+
+    # key, type, timeout
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    $idata->lock( "foo", "read" );
+    $idata->lock( "foo", "write" );
+
+    alarm 0;
+};
+like( $@, qr/timeout/, "default spinlock" );
+
+Test::IData->setup;
+
+diag "testing a spinlock, this will be a litle slow...";
+
+eval {
+    local $SIG{ALRM} = sub { alarm 0; die "timeout"; };
+    alarm 12;
+
+    # key, type, timeout
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    $idata->lock( "foo", "read" );
+    unless ( $idata->lock( "foo", "write" ) ) {
+        alarm 0;
+        die "gaveup";
+    }
+
+    alarm 0;
+};
+like( $@, qr/gaveup/, "default spinlock" );
+
+Test::IData->setup;
+
+diag "testing a spinlock, this will be a litle slow...";
+
+eval {
+    local $SIG{ALRM} = sub { alarm 0; die "timeout"; };
+    alarm 3;
+
+    # key, type, timeout
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    $idata->lock( "foo", "read" );
+    $idata->lock( "foo", "write", 5 );
+
+    alarm 0;
+};
+like( $@, qr/timeout/, "specified spinlock" );
+
+Test::IData->setup;
+
+diag "testing a spinlock, this will be a litle slow...";
+
+eval {
+    local $SIG{ALRM} = sub { alarm 0; die "timeout"; };
+    alarm 7;
+
+    # key, type, timeout
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    $idata->lock( "foo", "read" );
+    unless ( $idata->lock( "foo", "write", 5 ) ) {
+        alarm 0;
+        die "gaveup";
+    }
+
+    alarm 0;
+};
+
+like( $@, qr/gaveup/, "specified spinlock" );
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    $idata->lock( "foo", "write" );
+    is( $idata->lock( "foo", "write", 1 ), undef, "can not write lock twice" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    $idata->lock( "foo", "read" );
+    is( $idata->lock( "foo", "write", 1 ), undef, "can not write lock after read lock" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    $idata->lock( "foo", "write" );
+    is( $idata->lock( "foo", "read", 1 ), undef, "can not read lock after write lock" );
+}
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    $idata->lock();
+};
+like( $@, qr/2 - 3 were expected/, "no params" );
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    $idata->lock( "foo" );
+};
+like( $@, qr/2 - 3 were expected/, "not enough params" );
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    $idata->lock( "foo", "both" );
+};
+like( $@, qr/is read or write/, "not read or write" );
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    $idata->lock( "foo", 'read', 3, "bar" );
+};
+like( $@, qr/2 - 3 were expected/, "too many params" );
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous/t/15_client_unlock.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/t/15_client_unlock.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/t/15_client_unlock.t	(revision 22254)
@@ -0,0 +1,166 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Apache::Test qw( -withtestmore );
+
+plan tests => 14;
+
+use lib qw( ./t ./lib );
+
+use PS::IPP::IData::Client;
+use PS::IPP::IData::Util qw( :standard );
+use Test::IData;
+
+my $hostport = Apache::Test->config->{ 'hostport' };
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+    $idata->lock( "foo", "read" );
+
+    ok( $idata->unlock( "foo", "read" ), "read unlock" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+    $idata->lock( "foo", "read" );
+    $idata->lock( "foo", "read" );
+
+    ok( $idata->unlock( "foo", "read" ), "read unlock" );
+    ok( $idata->unlock( "foo", "read" ), "read unlock" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+    $idata->lock( "foo", "write" );
+
+    ok( $idata->unlock( "foo", "write" ), "write unlock" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    is( $idata->unlock( "foo", "read" ), undef, "storage object does not exist" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    is( $idata->unlock( "foo", "write" ), undef, "storage object does not exist" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    is( $idata->unlock( "foo", "read" ), undef, "no lock set" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+
+    is( $idata->unlock( "foo", "write" ), undef, "no lock set" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+    $idata->lock( "foo", "write" );
+
+    is( $idata->unlock( "foo", "read" ), undef, "read unlock under write lock" );
+}
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    $idata->create( "foo" );
+    $idata->lock( "foo", "read" );
+
+    is( $idata->unlock( "foo", "write" ), undef, "write unlock under read lock" );
+}
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    $idata->unlock();
+};
+like( $@, qr/2 were expected/, "no params" );
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    $idata->unlock( "foo" );
+};
+like( $@, qr/2 were expected/, "not enough params" );
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    $idata->unlock( "foo", "both" );
+};
+like( $@, qr/is read or write/, "not read or write" );
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    $idata->unlock( "foo", 'read', 3 );
+};
+like( $@, qr/2 were expected/, "too many params" );
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous/t/16_client_find_instances.t
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/t/16_client_find_instances.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/t/16_client_find_instances.t	(revision 22254)
@@ -0,0 +1,114 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Apache::Test qw( -withtestmore );
+
+plan tests => 7;
+
+use lib qw( ./t ./lib );
+
+use PS::IPP::IData::Client;
+use PS::IPP::IData::Util qw( :standard );
+use Test::IData;
+
+my $hostport = Apache::Test->config->{ 'hostport' };
+
+Test::IData->setup;
+
+SKIP: {
+    skip "need to write a module to validate URIs", 7;
+
+{
+    # key
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    my $uri = $idata->create( "foo" );
+
+    my $locations = $idata->find_instances( "foo" );
+
+    is( $uri, @$locations[0], "URIs match" );
+}
+
+Test::IData->setup;
+
+{
+    # key
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    my $uri1 = $idata->create( "foo" );
+    my $uri2 = $idata->replicate( "foo" );
+
+    my $locations = $idata->find_instances( "foo" );
+
+    ok( eq_set( [$uri1, $uri2], $locations ), "URIs match" );
+}
+
+Test::IData->setup;
+
+{
+    # key, volume
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    my $uri = $idata->create( "foo" );
+
+    my $locations = $idata->find_instances( "foo", "node01" );
+
+    is( $uri, $locations->[0], "URIs match" );
+}
+
+Test::IData->setup;
+
+{
+    # key
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+    my $uri1 = $idata->create( "foo" );
+    my $uri2 = $idata->replicate( "foo" );
+
+    my $locations = $idata->find_instances( "foo", "node01" );
+
+    ok( eq_set( [$uri1, $uri2], $locations ), "URIs match" );
+}
+
+
+Test::IData->setup;
+
+{
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    is( $idata->find_instances( "foo" ), undef, "storage object does not exist" );
+}
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    $idata->find_instances();
+};
+like( $@, qr/1 - 2 were expected/, "no params" );
+
+Test::IData->setup;
+
+eval {
+    my $idata = PS::IPP::IData::Client->new(
+        proxy => "http://$hostport/idata",
+    );
+
+    $idata->find_instances( "foo", 'read', 3 );
+};
+like( $@, qr/1 - 2 were expected/, "too many params" );
+
+Test::IData->cleanup;
+
+}
Index: /branches/jh_branches/jhoblitt/Nebulous/t/TEST.PL
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/t/TEST.PL	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/t/TEST.PL	(revision 22254)
@@ -0,0 +1,12 @@
+#file:t/TEST.PL
+#--------------
+#!perl
+
+use strict;
+use warnings FATAL => 'all';
+
+use lib qw(lib);
+
+use Apache::TestRunPerl ();
+
+Apache::TestRunPerl->new->run(@ARGV);
Index: /branches/jh_branches/jhoblitt/Nebulous/t/Test/Nebulous.pm
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/t/Test/Nebulous.pm	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/t/Test/Nebulous.pm	(revision 22254)
@@ -0,0 +1,113 @@
+# Copyright (C) 2004  Joshua Hoblitt
+#
+# $Id: Nebulous.pm,v 1.1.1.1 2004-12-22 02:16:23 jhoblitt Exp $
+
+package Test::IData;
+
+use strict;
+
+use DBI;
+use File::Path qw( mkpath rmtree );
+use File::Temp qw( tempdir );
+
+my $dsn     = "DBI:mysql:database=test:host=localhost";
+my $user    = "test";
+my $passwd  = "";
+
+my $dbh = DBI->connect( $dsn, $user, $passwd );
+
+#my $dir1 = tempdir( CLEANUP => 0 );
+#my $dir2 = tempdir( CLEANUP => 0 );
+
+my $dir1 = "/tmp/foo1";
+my $dir2 = "/tmp/foo1";
+
+
+sub setup {
+    my $self = shift;
+
+    $self->cleanup;
+
+    mkpath([$dir1, $dir2]);
+
+    local $/ = ';';
+
+    my $pos = tell DATA;
+
+    foreach my $statement (<DATA>) {
+        last unless ( $statement =~ /\S+/ );
+        $dbh->do( $statement );
+    }
+
+    seek DATA, $pos, 0;
+
+    # FIXME
+    $dbh->do( qq{ INSERT INTO volume VALUES (1,'node01',?) }, undef, 'file:' . $dir1 );
+    $dbh->do( qq{ INSERT INTO volume VALUES (2,'node02',?) }, undef, 'file:' . $dir2 );
+}
+
+sub cleanup {
+    $dbh->do( "DROP TABLE IF EXISTS storage_object" );
+    $dbh->do( "DROP TABLE IF EXISTS instance" );
+    $dbh->do( "DROP TABLE IF EXISTS lock_record" );
+    $dbh->do( "DROP TABLE IF EXISTS volume" );
+    $dbh->do( "DROP TABLE IF EXISTS class" );
+
+    rmtree([$dir1, $dir2], 0, 1);
+}
+
+1;
+
+__DATA__
+DROP TABLE IF EXISTS storage_object;
+CREATE TABLE storage_object (
+    so_id BIGINT NOT NULL AUTO_INCREMENT,
+    ext_id VARCHAR(255) BINARY NOT NULL UNIQUE,
+    class_id TINYINT NOT NULL,
+    comment VARCHAR(255),
+    read_lock TINYINT DEFAULT 0 NOT NULL,
+    write_lock ENUM( 'write' ),
+    epoch TIMESTAMP NOT NULL,
+    PRIMARY KEY(so_id),
+    KEY(ext_id(64)),
+    KEY(class_id)
+) ENGINE=innodb;
+
+DROP TABLE IF EXISTS instance;
+CREATE TABLE instance (
+    ins_id BIGINT NOT NULL AUTO_INCREMENT,
+    so_id BIGINT NOT NULL,
+    uri VARCHAR(255) BINARY NOT NULL UNIQUE,
+    sha1sum CHAR(40),
+    assigned_location BOOL,
+    epoch TIMESTAMP NOT NULL,
+    atime TIMESTAMP NOT NULL,
+    PRIMARY KEY(ins_id),
+    KEY(so_id),
+    KEY(uri(64))
+) ENGINE=innodb;
+
+DROP TABLE IF EXISTS lock_record;
+CREATE TABLE lock_record (
+        so_id BIGINT NOT NULL,
+        type ENUM( 'read', 'write' ) NOT NULL,
+        epoch TIMESTAMP,
+        KEY(so_ID)
+    ) ENGINE=innodb;
+
+DROP TABLE IF EXISTS volume;
+CREATE TABLE volume (
+        vol_id INT NOT NULL AUTO_INCREMENT,
+        name VARCHAR(255) UNIQUE BINARY NOT NULL,
+        uri VARCHAR(255) BINARY NOT NULL,
+        key(vol_id)
+    ) ENGINE=innodb;
+
+DROP TABLE IF EXISTS class;
+CREATE TABLE class (
+        class_id TINYINT NOT NULL AUTO_INCREMENT,
+        instance TINYINT NOT NULL,
+        priority TINYINT,
+        comment VARCHAR(255),
+        key(class_id)
+    ) ENGINE=innodb;
Index: /branches/jh_branches/jhoblitt/Nebulous/t/apache_03_server_create_object.x
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/t/apache_03_server_create_object.x	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/t/apache_03_server_create_object.x	(revision 22254)
@@ -0,0 +1,95 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Apache::Test qw(-withtestmore);
+
+plan tests => 11;
+
+use lib qw( ./t ./lib );
+
+use Apache::TestUtil;
+use PS::IPP::IData::Util qw( :standard );
+use SOAP::Lite;
+use Test::IData;
+
+my $hostport = Apache::Test::config()->{ 'hostport' };
+my $idata = SOAP::Lite->new(
+    proxy   => "http://$hostport/idata",
+    uri     => "http://pan-starrs.ifa.hawaii.edu/PS/IPP/IData/Server",
+);
+
+Test::IData->setup;
+
+{
+    # key
+    my $uri = $idata->create_object( "foo" )->result;
+
+    ok( -e _get_file_path( $uri ), "file exists" );
+
+    like( $uri, qr/^file:\//, "valid URI" );
+}
+
+Test::IData->setup;
+
+{
+    # key, class
+    my $uri = $idata->create_object( "foo", 0 )->result;
+    $idata->delete_instance( $uri );
+
+    ok( -e _get_file_path( $uri ), "file exists" );
+
+    like( $uri, qr/^file:\//, "valid URI" );
+}
+
+Test::IData->setup;
+
+{
+    # key, class, volume
+    my $uri = $idata->create_object( "foo", 0, "node01" )->result;
+    $idata->delete_instance( $uri );
+
+    ok( -e _get_file_path( $uri ), "file exists" );
+
+    like( $uri, qr/^file:\//, "valid URI" );
+}
+
+Test::IData->setup;
+
+{
+    # key, class, volume, comment
+    my $uri = $idata->create_object( "foo", 0, "node01", "this is foo" )->result;
+    $idata->delete_instance( $uri );
+
+    ok( -e _get_file_path( $uri ), "file exists" );
+
+    like( $uri, qr/^file:\//, "valid URI" );
+}
+
+Test::IData->setup;
+
+{
+    $idata->create_object( "foo" );
+    my $response = $idata->create_object( "foo" );
+
+    like( $response->faultstring, qr/Duplicate entry/, "object already exists" );
+}
+
+Test::IData->setup;
+
+{
+    my $response = $idata->create_object();
+
+    like( $response->faultstring, qr/2 - 4 were expected/, "no params" );
+}
+
+Test::IData->setup;
+
+{
+    my $response = $idata->create_object( 1, 2, 3, 4, 5 );
+
+    like( $response->faultstring, qr/2 - 4 were expected/, "too many params" );
+}
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous/t/apache_04_server_replicate_object.x
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/t/apache_04_server_replicate_object.x	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/t/apache_04_server_replicate_object.x	(revision 22254)
@@ -0,0 +1,86 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Apache::Test qw(-withtestmore);
+
+plan tests => 11;
+
+use lib qw( ./t ./lib );
+
+use Apache::TestUtil;
+use PS::IPP::IData::Util qw( :standard );
+use SOAP::Lite;
+use Test::IData;
+
+my $hostport = Apache::Test::config()->{ 'hostport' };
+my $idata = SOAP::Lite->new(
+    proxy   => "http://$hostport/idata",
+    uri     => "http://pan-starrs.ifa.hawaii.edu/PS/IPP/IData/Server",
+);
+
+Test::IData->setup;
+
+{
+    # key
+    $idata->create_object( "foo" );
+    my $uri = $idata->replicate_object( "foo" )->result;
+
+    ok( -e _get_file_path( $uri ), "file exists" );
+
+    like( $uri, qr/^file:\//, "valid URI" );
+}
+
+Test::IData->setup;
+
+{
+    # key, $volume
+    $idata->create_object( "foo" );
+    my $uri = $idata->replicate_object( "foo", "node01" )->result;
+
+    ok( -e _get_file_path( $uri ), "file exists" );
+
+    like( $uri, qr/^file:\//, "valid URI" );
+}
+
+Test::IData->setup;
+
+{
+    # key, $volume
+    $idata->create_object( "foo" );
+    my $uri1 = $idata->replicate_object( "foo", "node01" )->result;
+    my $uri2 = $idata->replicate_object( "foo", "node02" )->result;
+
+    ok( -e _get_file_path( $uri1 ), "file exists" );
+    ok( -e _get_file_path( $uri2 ), "file exists" );
+
+    like( $uri1, qr/^file:\//, "valid URI" );
+    like( $uri2, qr/^file:\//, "valid URI" );
+}
+
+Test::IData->setup;
+
+{
+    my $response = $idata->replicate_object();
+
+    like( $response->faultstring, qr/1 - 2 were expected/, "no params" );
+}
+
+Test::IData->setup;
+
+{
+    my $response = $idata->replicate_object( 1, 2, 3 );
+
+    like( $response->faultstring, qr/1 - 2 were expected/, "too many params" );
+}
+
+Test::IData->setup;
+
+{
+    my $response = $idata->replicate_object( "foo" );
+
+    like( $response->faultstring, qr/storage object does not exist/, "storage object does not exist" );
+}
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous/t/apache_05_server_lock_object.x
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/t/apache_05_server_lock_object.x	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/t/apache_05_server_lock_object.x	(revision 22254)
@@ -0,0 +1,129 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Apache::Test qw(-withtestmore);
+
+plan tests => 13;
+
+use lib qw( ./t ./lib );
+
+use Apache::TestUtil;
+use PS::IPP::IData::Util qw( :standard );
+use SOAP::Lite;
+use Test::IData;
+
+my $hostport = Apache::Test::config()->{ 'hostport' };
+my $idata = SOAP::Lite->new(
+    proxy   => "http://$hostport/idata",
+    uri     => "http://pan-starrs.ifa.hawaii.edu/PS/IPP/IData/Server",
+);
+
+Test::IData->setup;
+
+{
+    $idata->create_object( "foo" );
+
+    ok( $idata->lock_object( "foo", "read" )->result, "read lock" );
+}
+
+Test::IData->setup;
+
+{
+    $idata->create_object( "foo" );
+
+    ok( $idata->lock_object( "foo", "read" )->result, "read lock" );
+    ok( $idata->lock_object( "foo", "read" )->result, "read lock" );
+}
+
+Test::IData->setup;
+
+{
+    $idata->create_object( "foo" );
+
+    ok( $idata->lock_object( "foo", "write" )->result, "write lock" );
+}
+
+Test::IData->setup;
+
+{
+    my $result = $idata->lock_object( "foo", "read" );
+
+    like( $result->faultstring, qr/storage object does not exist/, "storage object does not exist" );
+}
+
+Test::IData->setup;
+
+{
+    my $result = $idata->lock_object( "foo", "write" );
+
+    like( $result->faultstring, qr/storage object does not exist/, "storage object does not exist" );
+}
+
+Test::IData->setup;
+
+{
+    $idata->create_object( "foo" );
+
+    $idata->lock_object( "foo", "write" );
+    my $result = $idata->lock_object( "foo", "write" );
+
+    like( $result->faultstring, qr/can not write lock twice/, "can not write lock twice" );
+}
+
+Test::IData->setup;
+
+{
+    $idata->create_object( "foo" );
+
+    $idata->lock_object( "foo", "read" );
+    my $result = $idata->lock_object( "foo", "write" );
+
+    like( $result->faultstring, qr/can not write lock after read lock/, "can not write lock after read lock" );
+}
+
+Test::IData->setup;
+
+{
+    $idata->create_object( "foo" );
+
+    $idata->lock_object( "foo", "write" );
+    my $result = $idata->lock_object( "foo", "read" );
+
+    like( $result->faultstring, qr/can not read lock after write lock/, "can not read lock after write lock" );
+}
+
+Test::IData->setup;
+
+{
+    my $result = $idata->lock_object();
+
+    like( $result->faultstring, qr/2 were expected/, "no params" );
+}
+
+Test::IData->setup;
+
+{
+    my $result = $idata->lock_object( "foo" );
+
+    like( $result->faultstring, qr/2 were expected/, "not enough params" );
+}
+
+Test::IData->setup;
+
+{
+    my $result = $idata->lock_object( "foo", "both" );
+
+    like( $result->faultstring, qr/is read or write/, "not read or write" );
+}
+
+Test::IData->setup;
+
+{
+    my $result = $idata->lock_object( "foo", 'read', 3 );
+
+    like( $result->faultstring, qr/2 were expected/, "too many params" );
+}
+
+Test::IData->cleanup;
Index: /branches/jh_branches/jhoblitt/Nebulous/t/conf/extra.conf.in
===================================================================
--- /branches/jh_branches/jhoblitt/Nebulous/t/conf/extra.conf.in	(revision 22254)
+++ /branches/jh_branches/jhoblitt/Nebulous/t/conf/extra.conf.in	(revision 22254)
@@ -0,0 +1,31 @@
+#file:t/conf/extra.conf.in
+#-------------------------
+# this file will be Include-d by @ServerRoot@/httpd.conf
+
+# where PS::IPP::IData::Server::Apache can be found
+#PerlSwitches -I@ServerRoot@/../lib
+<perl>
+use lib qw( @ServerRoot@/../lib );
+</perl>
+
+PerlModule Apache::DBI
+PerlModule DBI
+PerlModule PS::IPP::IData::Server::Apache
+PerlModule PS::IPP::IData::Server
+
+<perl>
+Apache->push_handlers(
+    PerlChildInitHandler => sub {
+        PS::IPP::IData::Server->setup(
+            "DBI:mysql:database=test:host=localhost",
+            "test",
+            "",
+        );
+    },
+);
+</perl>
+
+<Location /idata>
+    SetHandler perl-script
+    PerlHandler PS::IPP::IData::Server::Apache
+</Location>
Index: /branches/jh_branches/jhoblitt/PS-IPP-MetaDB/Build.PL
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-MetaDB/Build.PL	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-MetaDB/Build.PL	(revision 22254)
@@ -0,0 +1,9 @@
+use Module::Build;
+# See perldoc Module::Build for details of how this works
+
+Module::Build->new(
+    module_name     => 'PS::IPP::MetaDB',
+    build_requires  => {
+      'Class::DBI::mysql'   => 0,
+    },
+)->create_build_script;
Index: /branches/jh_branches/jhoblitt/PS-IPP-MetaDB/Changes
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-MetaDB/Changes	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-MetaDB/Changes	(revision 22254)
@@ -0,0 +1,6 @@
+Revision history for Perl module PS::IPP::MetaDB
+
+0.01 Tue Sep 20 12:20:39 2005
+    - original version; created by ExtUtils::ModuleMaker 0.41
+
+
Index: /branches/jh_branches/jhoblitt/PS-IPP-MetaDB/LICENSE
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-MetaDB/LICENSE	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-MetaDB/LICENSE	(revision 22254)
@@ -0,0 +1,260 @@
+The General Public License (GPL)
+Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave,
+Cambridge, MA 02139, USA. Everyone is permitted to copy and distribute
+verbatim copies of this license document, but changing it is not allowed.
+
+Preamble
+
+The licenses for most software are designed to take away your freedom to share
+and change it. By contrast, the GNU General Public License is intended to
+guarantee your freedom to share and change free software--to make sure the
+software is free for all its users. This General Public License applies to most of
+the Free Software Foundation's software and to any other program whose
+authors commit to using it. (Some other Free Software Foundation software is
+covered by the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+When we speak of free software, we are referring to freedom, not price. Our
+General Public Licenses are designed to make sure that you have the freedom
+to distribute copies of free software (and charge for this service if you wish), that
+you receive source code or can get it if you want it, that you can change the
+software or use pieces of it in new free programs; and that you know you can do
+these things.
+
+To protect your rights, we need to make restrictions that forbid anyone to deny
+you these rights or to ask you to surrender the rights. These restrictions
+translate to certain responsibilities for you if you distribute copies of the
+software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether gratis or for a
+fee, you must give the recipients all the rights that you have. You must make
+sure that they, too, receive or can get the source code. And you must show
+them these terms so they know their rights.
+
+We protect your rights with two steps: (1) copyright the software, and (2) offer
+you this license which gives you legal permission to copy, distribute and/or
+modify the software.
+
+Also, for each author's protection and ours, we want to make certain that
+everyone understands that there is no warranty for this free software. If the
+software is modified by someone else and passed on, we want its recipients to
+know that what they have is not the original, so that any problems introduced by
+others will not reflect on the original authors' reputations.
+
+Finally, any free program is threatened constantly by software patents. We wish
+to avoid the danger that redistributors of a free program will individually obtain
+patent licenses, in effect making the program proprietary. To prevent this, we
+have made it clear that any patent must be licensed for everyone's free use or
+not licensed at all.
+
+The precise terms and conditions for copying, distribution and modification
+follow.
+
+GNU GENERAL PUBLIC LICENSE
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND
+MODIFICATION
+
+0. This License applies to any program or other work which contains a notice
+placed by the copyright holder saying it may be distributed under the terms of
+this General Public License. The "Program", below, refers to any such program
+or work, and a "work based on the Program" means either the Program or any
+derivative work under copyright law: that is to say, a work containing the
+Program or a portion of it, either verbatim or with modifications and/or translated
+into another language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not covered by
+this License; they are outside its scope. The act of running the Program is not
+restricted, and the output from the Program is covered only if its contents
+constitute a work based on the Program (independent of having been made by
+running the Program). Whether that is true depends on what the Program does.
+
+1. You may copy and distribute verbatim copies of the Program's source code as
+you receive it, in any medium, provided that you conspicuously and appropriately
+publish on each copy an appropriate copyright notice and disclaimer of warranty;
+keep intact all the notices that refer to this License and to the absence of any
+warranty; and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and you may at
+your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Program or any portion of it, thus
+forming a work based on the Program, and copy and distribute such
+modifications or work under the terms of Section 1 above, provided that you also
+meet all of these conditions:
+
+a) You must cause the modified files to carry prominent notices stating that you
+changed the files and the date of any change.
+
+b) You must cause any work that you distribute or publish, that in whole or in
+part contains or is derived from the Program or any part thereof, to be licensed
+as a whole at no charge to all third parties under the terms of this License.
+
+c) If the modified program normally reads commands interactively when run, you
+must cause it, when started running for such interactive use in the most ordinary
+way, to print or display an announcement including an appropriate copyright
+notice and a notice that there is no warranty (or else, saying that you provide a
+warranty) and that users may redistribute the program under these conditions,
+and telling the user how to view a copy of this License. (Exception: if the
+Program itself is interactive but does not normally print such an announcement,
+your work based on the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If identifiable
+sections of that work are not derived from the Program, and can be reasonably
+considered independent and separate works in themselves, then this License,
+and its terms, do not apply to those sections when you distribute them as
+separate works. But when you distribute the same sections as part of a whole
+which is a work based on the Program, the distribution of the whole must be on
+the terms of this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest your rights to
+work written entirely by you; rather, the intent is to exercise the right to control
+the distribution of derivative or collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program with the
+Program (or with a work based on the Program) on a volume of a storage or
+distribution medium does not bring the other work under the scope of this
+License.
+
+3. You may copy and distribute the Program (or a work based on it, under
+Section 2) in object code or executable form under the terms of Sections 1 and 2
+above provided that you also do one of the following:
+
+a) Accompany it with the complete corresponding machine-readable source
+code, which must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange; or,
+
+b) Accompany it with a written offer, valid for at least three years, to give any
+third party, for a charge no more than your cost of physically performing source
+distribution, a complete machine-readable copy of the corresponding source
+code, to be distributed under the terms of Sections 1 and 2 above on a medium
+customarily used for software interchange; or,
+
+c) Accompany it with the information you received as to the offer to distribute
+corresponding source code. (This alternative is allowed only for noncommercial
+distribution and only if you received the program in object code or executable
+form with such an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for making
+modifications to it. For an executable work, complete source code means all the
+source code for all modules it contains, plus any associated interface definition
+files, plus the scripts used to control compilation and installation of the
+executable. However, as a special exception, the source code distributed need
+not include anything that is normally distributed (in either source or binary form)
+with the major components (compiler, kernel, and so on) of the operating system
+on which the executable runs, unless that component itself accompanies the
+executable.
+
+If distribution of executable or object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the source
+code from the same place counts as distribution of the source code, even though
+third parties are not compelled to copy the source along with the object code.
+
+4. You may not copy, modify, sublicense, or distribute the Program except as
+expressly provided under this License. Any attempt otherwise to copy, modify,
+sublicense or distribute the Program is void, and will automatically terminate
+your rights under this License. However, parties who have received copies, or
+rights, from you under this License will not have their licenses terminated so long
+as such parties remain in full compliance.
+
+5. You are not required to accept this License, since you have not signed it.
+However, nothing else grants you permission to modify or distribute the Program
+or its derivative works. These actions are prohibited by law if you do not accept
+this License. Therefore, by modifying or distributing the Program (or any work
+based on the Program), you indicate your acceptance of this License to do so,
+and all its terms and conditions for copying, distributing or modifying the
+Program or works based on it.
+
+6. Each time you redistribute the Program (or any work based on the Program),
+the recipient automatically receives a license from the original licensor to copy,
+distribute or modify the Program subject to these terms and conditions. You
+may not impose any further restrictions on the recipients' exercise of the rights
+granted herein. You are not responsible for enforcing compliance by third parties
+to this License.
+
+7. If, as a consequence of a court judgment or allegation of patent infringement
+or for any other reason (not limited to patent issues), conditions are imposed on
+you (whether by court order, agreement or otherwise) that contradict the
+conditions of this License, they do not excuse you from the conditions of this
+License. If you cannot distribute so as to satisfy simultaneously your obligations
+under this License and any other pertinent obligations, then as a consequence
+you may not distribute the Program at all. For example, if a patent license would
+not permit royalty-free redistribution of the Program by all those who receive
+copies directly or indirectly through you, then the only way you could satisfy
+both it and this License would be to refrain entirely from distribution of the
+Program.
+
+If any portion of this section is held invalid or unenforceable under any particular
+circumstance, the balance of the section is intended to apply and the section as
+a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any patents or other
+property right claims or to contest validity of any such claims; this section has
+the sole purpose of protecting the integrity of the free software distribution
+system, which is implemented by public license practices. Many people have
+made generous contributions to the wide range of software distributed through
+that system in reliance on consistent application of that system; it is up to the
+author/donor to decide if he or she is willing to distribute software through any
+other system and a licensee cannot impose that choice.
+
+This section is intended to make thoroughly clear what is believed to be a
+consequence of the rest of this License.
+
+8. If the distribution and/or use of the Program is restricted in certain countries
+either by patents or by copyrighted interfaces, the original copyright holder who
+places the Program under this License may add an explicit geographical
+distribution limitation excluding those countries, so that distribution is permitted
+only in or among countries not thus excluded. In such case, this License
+incorporates the limitation as if written in the body of this License.
+
+9. The Free Software Foundation may publish revised and/or new versions of the
+General Public License from time to time. Such new versions will be similar in
+spirit to the present version, but may differ in detail to address new problems or
+concerns.
+
+Each version is given a distinguishing version number. If the Program specifies a
+version number of this License which applies to it and "any later version", you
+have the option of following the terms and conditions either of that version or of
+any later version published by the Free Software Foundation. If the Program does
+not specify a version number of this License, you may choose any version ever
+published by the Free Software Foundation.
+
+10. If you wish to incorporate parts of the Program into other free programs
+whose distribution conditions are different, write to the author to ask for
+permission. For software which is copyrighted by the Free Software Foundation,
+write to the Free Software Foundation; we sometimes make exceptions for this.
+Our decision will be guided by the two goals of preserving the free status of all
+derivatives of our free software and of promoting the sharing and reuse of
+software generally.
+
+NO WARRANTY
+
+11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS
+NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE
+COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM
+"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR
+IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
+ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE,
+YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
+CORRECTION.
+
+12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED
+TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY
+WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS
+PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
+ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM
+(INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY
+OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS
+BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+END OF TERMS AND CONDITIONS
Index: /branches/jh_branches/jhoblitt/PS-IPP-MetaDB/MANIFEST
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-MetaDB/MANIFEST	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-MetaDB/MANIFEST	(revision 22254)
@@ -0,0 +1,8 @@
+MANIFEST
+README
+LICENSE
+Todo
+Changes
+Build.PL
+lib/PS/IPP/MetaDB.pm
+t/01_load.t
Index: /branches/jh_branches/jhoblitt/PS-IPP-MetaDB/README
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-MetaDB/README	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-MetaDB/README	(revision 22254)
@@ -0,0 +1,15 @@
+pod2text PS::IPP::MetaDB.pm > README
+
+If this is still here it means the programmer was too lazy to create the readme file.
+
+You can create it now by using the command shown above from this directory.
+
+At the very least you should be able to use this set of instructions
+to install the module...
+
+perl Build.PL
+./Build
+./Build test
+./Build install
+
+If you are on a windows box you should use 'nmake' rather than 'make'.
Index: /branches/jh_branches/jhoblitt/PS-IPP-MetaDB/Todo
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-MetaDB/Todo	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-MetaDB/Todo	(revision 22254)
@@ -0,0 +1,5 @@
+TODO list for Perl module PS::IPP::MetaDB
+
+- Nothing yet
+
+
Index: /branches/jh_branches/jhoblitt/PS-IPP-MetaDB/lib/PS/IPP/MetaDB.pm
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-MetaDB/lib/PS/IPP/MetaDB.pm	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-MetaDB/lib/PS/IPP/MetaDB.pm	(revision 22254)
@@ -0,0 +1,376 @@
+# Copyright (C) 2005  Joshua Hoblitt
+#
+# $Id: MetaDB.pm,v 1.1.1.1 2005-09-28 01:21:27 jhoblitt Exp $
+
+package PS::IPP::MetaDB;
+
+use strict;
+use warnings FATAL => qw( all );
+
+use vars qw( $VERSION ); 
+$VERSION = '0.01';
+
+use base qw( Class::DBI::mysql );
+
+__PACKAGE__->set_db('Main', 'dbi:mysql:test', 'test', '');
+
+#package PS::IPP::MetaDB::Image::Prepared;
+#package PS::IPP::MetaDB::Image::Reduced;
+#package PS::IPP::MetaDB::Image::Analyzed;
+#package PS::IPP::MetaDB::Image::Combined;
+
+=pod
+
+=head2 Temporal Queues
+
+These queues all describe work that I<needs> to be performed but hasn't
+happened yet.
+
+=over 4
+
+=item * new
+
+This table contains a list of new data waiting to be downloaded from the
+summit.  The location of fetched data files is written into the C<newed> table.
+
+Table description:
+
+=over 4
+
+=item * url
+
+URL to retrieve data from
+
+=item * exp_id
+
+exposure ID #
+
+=item * camera
+
+Camera that this data was acquired from
+
+=item * class
+
+The 'type' of data this is
+
+=item * class_id
+
+A unique identifier for data of the same class in the same exposure.
+
+=back
+
+=cut
+
+package PS::IPP::MetaDB::Image::New;
+
+use base qw( PS::IPP::MetaDB );
+
+__PACKAGE__->table("new");
+__PACKAGE__->create_table(q{
+    exp_id      BIGINT          NOT NULL PRIMARY KEY,
+    class_id    BIGINT          NOT NULL,
+    camera      VARCHAR(255),
+    class       VARCHAR(255),
+    url         VARCHAR(255)    NOT NULL
+});
+__PACKAGE__->set_up_table;
+
+=item * raw
+
+Phase 0.  A list of exposure IDs to extract metadata from.
+
+=over 4
+
+=item * exp_id
+
+exposure ID #, keyed against the C<newed> table.
+
+=item * class_id
+
+Class ID #, keyed against the C<newed> table.
+
+=back
+
+=cut
+
+package PS::IPP::MetaDB::Image::Raw;
+
+use base qw( PS::IPP::MetaDB );
+
+__PACKAGE__->table("raw");
+__PACKAGE__->create_table(q{
+    exp_id      BIGINT          NOT NULL PRIMARY KEY,
+    class_id    BIGINT          NOT NULL 
+});
+__PACKAGE__->set_up_table;
+
+=item * prepare
+
+Phase 1: A list of exposure IDs to extra metadata from.
+
+=over 4
+
+=item * exp_id
+
+exposure ID #, keyed against the C<newed> table.
+
+=item * exp_config
+
+Class ID #, keyed against the C<exposure_config> table.
+
+=back
+
+=item * reduce
+
+Phase 2: A list of exposure components to reduce.
+
+=over 4
+
+=item * exp_id
+
+exposure ID #, keyed against the C<prepared> table.
+
+=item * class_id
+
+class ID #, keyed against the C<prepared> table.
+
+=item * prepared_ver
+
+version #, keyed against the C<prepared> table.
+
+=item * dest_ver
+
+version # to be written into the C<reduced> table.
+
+=back
+
+=item * mosaic
+
+Phase 3: A list of exposures to process.
+
+=over 4
+
+=item * exp_id
+
+exposure ID #, keyed against the C<reduced> table.
+
+=item * exp_config
+
+Class ID #, keyed against the C<exposure_config> table.
+
+=back
+
+=item * combine
+
+=over 4
+
+=item * run_id???
+
+=back
+
+=back
+
+=head2 Metadata
+
+These tables describe data in other tables.
+
+=over 4
+
+=item * exposure
+
+A list of ALL exposure ID #s.
+
+=over 4
+
+=item * exp_id
+
+exposure ID #.
+
+=item * camera
+
+Name of the camera the generated the exposure.
+
+=item * type
+
+The type of exposure taken.
+
+=item * n_components
+
+The number of file that make of the complete exposure.
+
+=back
+
+=item * exposure_component
+
+A list of the files that together make up a complete exposure.
+
+=over 4
+
+=item * exp_id
+
+exposure ID #, keyed against the C<exposure> table.
+
+=item * class_id
+
+class ID #.
+
+=item * class
+
+The type of component this is
+
+=back
+
+=item * exposure_config
+
+This table provides a way to configure which 'version' of components of an
+'exposure' to use.
+
+=over 4
+
+=item * exp_config
+
+exposure config ID #.
+
+=item * class_id
+
+class ID #, keyed against the C<exposure_component> table.
+
+=item * version
+
+version ID #, keyed against the C<exposure_component> table.
+
+=item * n_components
+
+=back
+
+=item * exposure_reduced
+
+A list of version ID #'s assigned after reducing data.  This table is used to
+make sure that all components of an exposure get the same version ID assigned
+to them.
+
+=over 4
+
+=item * exp_id
+
+exposure ID #, keyed on the C<exposure> table.
+
+=item * reduced_version
+
+Version ID # used.
+
+=back
+
+=back
+
+=head2 File Storage
+
+These tables describe data actually stored on disk.
+
+=over 4
+
+=item * newed
+
+Note that there is only one 'version' of the original data.
+
+=over 4
+
+=item * exp_id
+
+exposure ID #, keyed on the C<exposure_component> table.
+
+=item * class_id
+
+class ID #, keyed on the C<exposure_componet> table.
+
+=item * url
+
+url to the storage location of the file.
+
+=back
+
+=cut
+
+package PS::IPP::MetaDB::Image::Newed;
+
+use base qw( PS::IPP::MetaDB );
+
+__PACKAGE__->table("newed");
+__PACKAGE__->create_table(q{
+    exp_id      BIGINT          NOT NULL PRIMARY KEY,
+    class_id    BIGINT          NOT NULL,
+    url         VARCHAR(255)    NOT NULL
+});
+__PACKAGE__->set_up_table;
+
+=item * prepared
+
+Data that has been through Phase: 0.
+
+=over 4
+
+=item * exp_id
+
+exposure ID #, keyed on the C<exposure_component> table.
+
+=item * class_id
+
+class ID #, keyed on the C<exposure_componet> table.
+
+=item * version
+
+version ID #
+
+=item * recipe
+
+algorithm ID #
+
+=item * url
+
+url to the storage location of the file.
+
+=back
+
+=item * reduced
+
+Data that's been through Phase: 2.
+
+=over 4
+
+=item * exp_id
+
+exposure ID #, keyed on the C<exposure_component> table.
+
+=item * class_id
+
+class ID #, keyed on the C<exposure_componet> table.
+
+=item * version
+
+version ID #
+
+=item * recipe
+
+algorithm ID #
+
+=item * url
+
+url to the storage location of the file.
+
+=back
+
+=item * mosaiced
+
+???
+
+=item * combined
+
+???
+
+=back
+
+=cut
+
+1;
+
+__END__
Index: /branches/jh_branches/jhoblitt/PS-IPP-MetaDB/t/01_load.t
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-MetaDB/t/01_load.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-MetaDB/t/01_load.t	(revision 22254)
@@ -0,0 +1,12 @@
+# -*- perl -*-
+
+# t/001_load.t - check module loading and create testing directory
+
+use Test::More tests => 2;
+
+BEGIN { use_ok( 'PS::IPP::MetaDB' ); }
+
+my $object = PS::IPP::MetaDB->new ();
+isa_ok ($object, 'PS::IPP::MetaDB');
+
+
Index: /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/Build.PL
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/Build.PL	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/Build.PL	(revision 22254)
@@ -0,0 +1,14 @@
+use Module::Build;
+# See perldoc Module::Build for details of how this works
+
+Module::Build->new(
+    module_name         => 'PS::IPP::Metadata::Config',
+    dist_version_from   => 'lib/PS/IPP/Metadata/Config.pm',
+    author              => 'Joshua Hoblitt <jhoblitt@cpan.org>',
+    license             => 'gpl',
+    requires            =>  {
+        'Parse::RecDescent'     => '1.94',
+        'Class::Accessor::Fast' => '0.19',
+        'Carp'                  => 0,
+    },   
+)->create_build_script;
Index: /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/Changes
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/Changes	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/Changes	(revision 22254)
@@ -0,0 +1,6 @@
+Revision history for Perl module PS::IPP::Metadata::Config
+
+0.01 Tue Feb 22 15:46:35 2005
+	- original version; created by ExtUtils::ModuleMaker 0.32
+
+
Index: /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/LICENSE
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/LICENSE	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/LICENSE	(revision 22254)
@@ -0,0 +1,260 @@
+The General Public License (GPL)
+Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave,
+Cambridge, MA 02139, USA. Everyone is permitted to copy and distribute
+verbatim copies of this license document, but changing it is not allowed.
+
+Preamble
+
+The licenses for most software are designed to take away your freedom to share
+and change it. By contrast, the GNU General Public License is intended to
+guarantee your freedom to share and change free software--to make sure the
+software is free for all its users. This General Public License applies to most of
+the Free Software Foundation's software and to any other program whose
+authors commit to using it. (Some other Free Software Foundation software is
+covered by the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+When we speak of free software, we are referring to freedom, not price. Our
+General Public Licenses are designed to make sure that you have the freedom
+to distribute copies of free software (and charge for this service if you wish), that
+you receive source code or can get it if you want it, that you can change the
+software or use pieces of it in new free programs; and that you know you can do
+these things.
+
+To protect your rights, we need to make restrictions that forbid anyone to deny
+you these rights or to ask you to surrender the rights. These restrictions
+translate to certain responsibilities for you if you distribute copies of the
+software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether gratis or for a
+fee, you must give the recipients all the rights that you have. You must make
+sure that they, too, receive or can get the source code. And you must show
+them these terms so they know their rights.
+
+We protect your rights with two steps: (1) copyright the software, and (2) offer
+you this license which gives you legal permission to copy, distribute and/or
+modify the software.
+
+Also, for each author's protection and ours, we want to make certain that
+everyone understands that there is no warranty for this free software. If the
+software is modified by someone else and passed on, we want its recipients to
+know that what they have is not the original, so that any problems introduced by
+others will not reflect on the original authors' reputations.
+
+Finally, any free program is threatened constantly by software patents. We wish
+to avoid the danger that redistributors of a free program will individually obtain
+patent licenses, in effect making the program proprietary. To prevent this, we
+have made it clear that any patent must be licensed for everyone's free use or
+not licensed at all.
+
+The precise terms and conditions for copying, distribution and modification
+follow.
+
+GNU GENERAL PUBLIC LICENSE
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND
+MODIFICATION
+
+0. This License applies to any program or other work which contains a notice
+placed by the copyright holder saying it may be distributed under the terms of
+this General Public License. The "Program", below, refers to any such program
+or work, and a "work based on the Program" means either the Program or any
+derivative work under copyright law: that is to say, a work containing the
+Program or a portion of it, either verbatim or with modifications and/or translated
+into another language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not covered by
+this License; they are outside its scope. The act of running the Program is not
+restricted, and the output from the Program is covered only if its contents
+constitute a work based on the Program (independent of having been made by
+running the Program). Whether that is true depends on what the Program does.
+
+1. You may copy and distribute verbatim copies of the Program's source code as
+you receive it, in any medium, provided that you conspicuously and appropriately
+publish on each copy an appropriate copyright notice and disclaimer of warranty;
+keep intact all the notices that refer to this License and to the absence of any
+warranty; and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and you may at
+your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Program or any portion of it, thus
+forming a work based on the Program, and copy and distribute such
+modifications or work under the terms of Section 1 above, provided that you also
+meet all of these conditions:
+
+a) You must cause the modified files to carry prominent notices stating that you
+changed the files and the date of any change.
+
+b) You must cause any work that you distribute or publish, that in whole or in
+part contains or is derived from the Program or any part thereof, to be licensed
+as a whole at no charge to all third parties under the terms of this License.
+
+c) If the modified program normally reads commands interactively when run, you
+must cause it, when started running for such interactive use in the most ordinary
+way, to print or display an announcement including an appropriate copyright
+notice and a notice that there is no warranty (or else, saying that you provide a
+warranty) and that users may redistribute the program under these conditions,
+and telling the user how to view a copy of this License. (Exception: if the
+Program itself is interactive but does not normally print such an announcement,
+your work based on the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If identifiable
+sections of that work are not derived from the Program, and can be reasonably
+considered independent and separate works in themselves, then this License,
+and its terms, do not apply to those sections when you distribute them as
+separate works. But when you distribute the same sections as part of a whole
+which is a work based on the Program, the distribution of the whole must be on
+the terms of this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest your rights to
+work written entirely by you; rather, the intent is to exercise the right to control
+the distribution of derivative or collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program with the
+Program (or with a work based on the Program) on a volume of a storage or
+distribution medium does not bring the other work under the scope of this
+License.
+
+3. You may copy and distribute the Program (or a work based on it, under
+Section 2) in object code or executable form under the terms of Sections 1 and 2
+above provided that you also do one of the following:
+
+a) Accompany it with the complete corresponding machine-readable source
+code, which must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange; or,
+
+b) Accompany it with a written offer, valid for at least three years, to give any
+third party, for a charge no more than your cost of physically performing source
+distribution, a complete machine-readable copy of the corresponding source
+code, to be distributed under the terms of Sections 1 and 2 above on a medium
+customarily used for software interchange; or,
+
+c) Accompany it with the information you received as to the offer to distribute
+corresponding source code. (This alternative is allowed only for noncommercial
+distribution and only if you received the program in object code or executable
+form with such an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for making
+modifications to it. For an executable work, complete source code means all the
+source code for all modules it contains, plus any associated interface definition
+files, plus the scripts used to control compilation and installation of the
+executable. However, as a special exception, the source code distributed need
+not include anything that is normally distributed (in either source or binary form)
+with the major components (compiler, kernel, and so on) of the operating system
+on which the executable runs, unless that component itself accompanies the
+executable.
+
+If distribution of executable or object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the source
+code from the same place counts as distribution of the source code, even though
+third parties are not compelled to copy the source along with the object code.
+
+4. You may not copy, modify, sublicense, or distribute the Program except as
+expressly provided under this License. Any attempt otherwise to copy, modify,
+sublicense or distribute the Program is void, and will automatically terminate
+your rights under this License. However, parties who have received copies, or
+rights, from you under this License will not have their licenses terminated so long
+as such parties remain in full compliance.
+
+5. You are not required to accept this License, since you have not signed it.
+However, nothing else grants you permission to modify or distribute the Program
+or its derivative works. These actions are prohibited by law if you do not accept
+this License. Therefore, by modifying or distributing the Program (or any work
+based on the Program), you indicate your acceptance of this License to do so,
+and all its terms and conditions for copying, distributing or modifying the
+Program or works based on it.
+
+6. Each time you redistribute the Program (or any work based on the Program),
+the recipient automatically receives a license from the original licensor to copy,
+distribute or modify the Program subject to these terms and conditions. You
+may not impose any further restrictions on the recipients' exercise of the rights
+granted herein. You are not responsible for enforcing compliance by third parties
+to this License.
+
+7. If, as a consequence of a court judgment or allegation of patent infringement
+or for any other reason (not limited to patent issues), conditions are imposed on
+you (whether by court order, agreement or otherwise) that contradict the
+conditions of this License, they do not excuse you from the conditions of this
+License. If you cannot distribute so as to satisfy simultaneously your obligations
+under this License and any other pertinent obligations, then as a consequence
+you may not distribute the Program at all. For example, if a patent license would
+not permit royalty-free redistribution of the Program by all those who receive
+copies directly or indirectly through you, then the only way you could satisfy
+both it and this License would be to refrain entirely from distribution of the
+Program.
+
+If any portion of this section is held invalid or unenforceable under any particular
+circumstance, the balance of the section is intended to apply and the section as
+a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any patents or other
+property right claims or to contest validity of any such claims; this section has
+the sole purpose of protecting the integrity of the free software distribution
+system, which is implemented by public license practices. Many people have
+made generous contributions to the wide range of software distributed through
+that system in reliance on consistent application of that system; it is up to the
+author/donor to decide if he or she is willing to distribute software through any
+other system and a licensee cannot impose that choice.
+
+This section is intended to make thoroughly clear what is believed to be a
+consequence of the rest of this License.
+
+8. If the distribution and/or use of the Program is restricted in certain countries
+either by patents or by copyrighted interfaces, the original copyright holder who
+places the Program under this License may add an explicit geographical
+distribution limitation excluding those countries, so that distribution is permitted
+only in or among countries not thus excluded. In such case, this License
+incorporates the limitation as if written in the body of this License.
+
+9. The Free Software Foundation may publish revised and/or new versions of the
+General Public License from time to time. Such new versions will be similar in
+spirit to the present version, but may differ in detail to address new problems or
+concerns.
+
+Each version is given a distinguishing version number. If the Program specifies a
+version number of this License which applies to it and "any later version", you
+have the option of following the terms and conditions either of that version or of
+any later version published by the Free Software Foundation. If the Program does
+not specify a version number of this License, you may choose any version ever
+published by the Free Software Foundation.
+
+10. If you wish to incorporate parts of the Program into other free programs
+whose distribution conditions are different, write to the author to ask for
+permission. For software which is copyrighted by the Free Software Foundation,
+write to the Free Software Foundation; we sometimes make exceptions for this.
+Our decision will be guided by the two goals of preserving the free status of all
+derivatives of our free software and of promoting the sharing and reuse of
+software generally.
+
+NO WARRANTY
+
+11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS
+NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE
+COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM
+"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR
+IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
+ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE,
+YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
+CORRECTION.
+
+12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED
+TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY
+WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS
+PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
+ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM
+(INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY
+OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS
+BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+END OF TERMS AND CONDITIONS
Index: /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/MANIFEST
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/MANIFEST	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/MANIFEST	(revision 22254)
@@ -0,0 +1,13 @@
+Build.PL
+Changes
+LICENSE
+MANIFEST
+Makefile.PL
+README
+Todo
+docs/sdrs_grammar.txt
+lib/PS/IPP/Metadata/Config.pm
+lib/PS/IPP/Metadata/Config.pod
+t/01_load.t
+t/02_sdrs_example.t
+t/03_gene_example.t
Index: /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/Makefile.PL
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/Makefile.PL	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/Makefile.PL	(revision 22254)
@@ -0,0 +1,27 @@
+unless (eval "use Module::Build::Compat 0.02; 1" ) {
+  print "This module requires Module::Build to install itself.\n";
+
+  require ExtUtils::MakeMaker;
+  my $yn = ExtUtils::MakeMaker::prompt
+    ('  Install Module::Build from CPAN?', 'y');
+
+  if ($yn =~ /^y/i) {
+    require Cwd;
+    require File::Spec;
+    require CPAN;
+
+    # Save this 'cause CPAN will chdir all over the place.
+    my $cwd = Cwd::cwd();
+    my $makefile = File::Spec->rel2abs($0);
+
+    CPAN::Shell->install('Module::Build::Compat');
+
+    chdir $cwd or die "Cannot chdir() back to $cwd: $!";
+    exec $^X, $makefile, @ARGV;  # Redo now that we have Module::Build
+  } else {
+    warn " *** Cannot install without Module::Build.  Exiting ...\n";
+    exit 1;
+  }
+}
+Module::Build::Compat->run_build_pl(args => \@ARGV);
+Module::Build::Compat->write_makefile();
Index: /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/README
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/README	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/README	(revision 22254)
@@ -0,0 +1,16 @@
+pod2text PS::IPP::Metadata::Config.pm > README
+
+If this is still here it means the programmer was too lazy to create the readme file.
+
+You can create it now by using the command shown above from this directory.
+
+At the very least you should be able to use this set of instructions
+to install the module...
+
+perl Build.PL
+./Build
+./Build test
+./Build install
+
+
+If you are on a windows box you should use 'nmake' rather than 'make'.
Index: /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/Todo
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/Todo	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/Todo	(revision 22254)
@@ -0,0 +1,5 @@
+TODO list for Perl module PS::IPP::Metadata::Config
+
+- Nothing yet
+
+
Index: /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/docs/sdrs_grammar.txt
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/docs/sdrs_grammar.txt	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/docs/sdrs_grammar.txt	(revision 22254)
@@ -0,0 +1,144 @@
+startrule: statement(s) /\z/
+
+statement: scalar | vector | multi_declare | comment
+
+comment: '#' end_of_line
+
+scalar: name type value comment | name type value 
+
+vector: vname vtype vvalue comment | vname vtype vvalue 
+
+multi_declare: name '*' end_of_line
+
+name: /[a-z][.\w]*/i
+
+vname: /\@[a-z][.\w]*/i
+
+type: 
+    vtype | 'STR' | 'STRING'
+
+vtype: 
+    'S8'  |
+    'S16' |
+    'S32' |
+    'S64' |
+    'U8'  |
+    'U16' |
+    'U32' |
+    'U64' |
+    'F32' |
+    'F64' |
+    'C32' |
+    'C64' |
+    'BOOL'
+
+value: vector_value | string
+
+vvalue: vector_value vector_sep vvalue | vector_value vector_sep(?)
+
+vector_sep: ','
+
+vector_value: float | int | bool
+
+int: integer_constant
+
+float: floating_constant
+
+bool: /[tf]\s+?/i
+
+string:
+    /\S[^#\n]*/
+
+end_of_line: /[^\n]*/
+
+### based on syntax found in "C A Reference Manual"
+
+# integer constants
+
+integer_constant:
+    decimal_constant integer_suffix(?) |
+    octal_constant integer_suffix(?) |
+    hexadecimal_constant integer_suffix(?)
+
+decimal_constant:
+    digit(2..) |
+    nonzero_digit
+
+octal_constant:
+    '0' octal_digit(s) |
+    '0'
+   
+hexadecimal_constant:
+    hex_prefix hex_digit_sequence
+
+digit:
+    /[0-9]/
+
+nonzero_digit:
+    /[1-9]/
+
+octal_digit:
+    /[0-7]/
+
+hex_digit:
+    /0_9a-f/i
+
+integer_suffix:
+    long_suffix unsigned_suffix(?) |
+    long_long_suffix unsigned_suffix(?) |
+    unsigned_suffix long_suffix(?) |
+    unsigned_suffix long_long_suffix(?)
+
+long_suffix:
+    /l/i
+
+long_long_suffix:
+    /ll/i
+
+unsigned_suffix:
+    /u/i
+
+# floating-point constants
+
+floating_constant:
+    decimal_floating_constant |
+    hexadecimal_floating_constant
+
+decimal_floating_constant:
+    digit_sequence exponent floating_suffix(?) |
+    dotted_digits exponent(?) floating_suffix(?)
+
+digit_sequence:
+    digit(s)
+
+dotted_digits:
+    digit_sequence '.' digit_sequence  |
+    '.' digit_sequence |
+    digit_sequence '.'
+
+exponent:
+    /e/i sign_part digit_sequence
+
+sign_part:
+    /[+-]/
+
+floating_suffix:
+    /[fl]/i
+
+hexadecimal_floating_constant:
+    hex_prefix dotted_hex_digits binary_exponent floating_suffix(?) |
+    hex_prefix hex_digit_sequence binary_exponent floating_suffix(?)
+
+hex_prefix:
+    /0x/i
+
+dotted_hex_digits:
+    hex_digit_sequence '.' hex_digit_sequence |
+    '.' hex_digit_sequence |
+    hex_digit_sequence '.'
+
+hex_digit_sequence:
+    hex_digit(s) 
+
+binary_exponent:
+    /p/i sign_part(?) digit_sequence
Index: /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/lib/PS/IPP/Metadata/Config.pm
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/lib/PS/IPP/Metadata/Config.pm	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/lib/PS/IPP/Metadata/Config.pm	(revision 22254)
@@ -0,0 +1,288 @@
+# Copyright (c) 2005  Joshua Hoblitt
+#
+# $Id#
+
+package PS::IPP::Metadata::Config;
+
+use strict;
+use warnings FATAL => qw( all );
+
+our $VERSION = '0.01';
+
+use Carp qw( carp );
+use Parse::RecDescent;
+#use Data::Dumper;
+
+use base qw( Class::Accessor::Fast );
+__PACKAGE__->mk_accessors( qw( overwrite ) );
+
+#$::RD_TRACE = 1;
+#$::RD_HINT = 1;
+
+my $grammar = do{ local $/; <DATA> };
+
+sub new {
+    my $class = shift;
+
+    my $myparser = Parse::RecDescent->new( $grammar );
+
+    my $self = { _parser => $myparser };
+
+    bless $self, $class;
+
+    $self->overwrite( undef );
+
+    return $self;
+}
+
+sub parse {
+    my ( $self, $metadata ) = @_;
+
+    my $tree = $self->{_parser}->startrule( $metadata );
+
+    # remove comment only lines
+    $self->_strip_comments( $tree ) or return undef;
+
+    # handle multi-declare lines
+    $self->_merge_multi_declares( $tree ) or return undef;
+
+    # look for duplicate names, there should be none after processing the
+    # multi-declares
+    $self->_merge_duplicates( $tree ) or return undef;
+
+    return $tree;
+}
+
+sub _strip_comments {
+    my ( $self, $tree ) = @_;
+
+    # Iteratate through the AoH structure and remove the hashes that have a
+    # class key equal to "comment_line".
+    for (my $i = 0; $i < @{$tree}; $i++) {
+        my $elem = $tree->[$i];
+
+        if ( $elem->{class} eq "comment_line" ) {
+            # delete element from array
+            splice @$tree, $i, 1;
+
+            # the list just got shorter by one element so we don't want to
+            # increment the cursor
+            redo;
+        }
+    }
+
+    return 1;
+}
+
+sub _merge_multi_declares {
+    my ( $self, $tree ) = @_;
+
+    # list of valid multi-declares
+    my %multis;
+
+    # Iteratate through the AoH structure looking for multi-declare
+    # declarations.  This is done in a single pass so multi-declares must
+    # actually be 'declared' before any member elements appear.  
+    for (my $i = 0; $i < @{$tree}; $i++) {
+        my $elem = $tree->[$i];
+
+        if ( $elem->{class} eq "multi_declare" ) {
+            # construct a valid parse spec
+            my $node = {
+                class   => 'vector',
+                name    => $elem->{name},
+                type    => 'STR',   # only strings are allowed
+                value   => [],
+            };
+
+            # add to list of valid multi-declares
+            $multis{ $elem->{name} } = $node;
+
+            # delete mult-declare hash from the array and replace it with the
+            # valid spec
+            splice @$tree, $i, 1, $node;
+
+            next;
+        }
+
+        if ( my $node = $multis{ $elem->{name} } ) {
+            if ( ! $elem->{type} =~ /STR|STRING/ ) {
+                carp "multi-declare element, ",
+                     $elem->{name},
+                     "has type of ",
+                     $elem->{type},
+                     "instead of STR|STRING\n";
+                return undef;
+            }
+
+            # add element value to the appropriate multi-declare element
+            my $vec = $node->{value};
+            push( @$vec, $elem->{value} );
+
+            # delete element from array
+            splice @$tree, $i, 1;
+
+            # the list just got shorter by one element so we don't want to
+            # increment the cursor
+            redo;
+        }
+    }
+
+    return 1;
+}
+
+sub _merge_duplicates {
+    my ( $self, $tree ) = @_;
+
+    # encountered names
+    my %names;
+
+    for (my $i = 0; $i < @{$tree}; $i++) {
+        my $elem = $tree->[$i];
+
+        if ( exists $names{ $elem->{name} } ) {
+            carp "duplicate element name, ", $elem->{name}, "\n";
+
+            if ( $self->{overwrite} ) {
+                # remove the previous occurance
+                splice @{$tree}, $names{ $elem->{name} }, 1;
+            } else {
+                # remove the current occurance
+                splice @{$tree}, $i, 1;
+            }
+        }
+
+        # add element name and location to record of previously seen names
+        $names{ $elem->{name} } = $i;
+    }
+
+    return 1;
+}
+
+1;
+
+__DATA__
+
+startrule:
+    statement(s) /\z/
+        { $item[1 .. -1] }
+
+statement:
+    scalar
+    | vector
+    | multi_declare
+    | comment_line
+
+comment_line:
+    comment
+        {{
+            class   => 'comment_line',
+            comment => $item{comment}
+        }}
+
+comment:
+    '#' end_of_line
+        { $item{end_of_line} }
+
+scalar:
+    name type value comment
+        {{
+            class   => 'scalar',
+            name    => $item{name},
+            type    => $item{type},
+            value   => $item{value},
+            comment => $item{comment}
+         }}
+    | name type value 
+        {{
+            class   => 'scalar',
+            name    => $item{name},
+            type    => $item{type},
+            value   => $item{value},
+        }}
+
+vector:
+    vname vtype vvalue comment
+        {{
+            class   => 'vector',
+            name    => $item{vname},
+            type    => $item{vtype},
+            value   => $item{vvalue},
+            comment => $item{comment}
+         }}
+    | vname vtype vvalue 
+        {{
+            class   => 'vector',
+            name    => $item{vname},
+            type    => $item{vtype},
+            value   => $item{vvalue},
+         }}
+
+multi_declare:
+    name '*' end_of_line
+        {{
+            class   => 'multi_declare',
+            name    => $item{name}, 
+            comment => $item{end_of_line},
+        }}
+
+name:
+    /[a-z][.\w]*/i
+
+vname:
+    /\@[a-z][.\w]*/i
+
+type: 
+    vtype | 'STR' | 'STRING'
+
+vtype: 
+    'S8'
+    | 'S16'
+    | 'S32'
+    | 'S64'
+    | 'U8' 
+    | 'U16'
+    | 'U32'
+    | 'U64'
+    | 'F32'
+    | 'F64'
+    | 'C32'
+    | 'C64'
+    | 'BOOL'
+
+value:
+    primitive | string
+
+
+vvalue:
+    vvalue_(s)
+        { [ map { @$_ } @{$item[1]} ] }
+
+# backtracking optimization
+vvalue_:
+    primitive vector_sep(?)
+        { [ $item[1] ] }
+
+vector_sep:
+    /,|\s+/
+
+primitive:
+    bool | float | int
+
+int:
+    # $RE{num}{int} from Regexp::Common::number
+    /(?:(?:[+-]?)(?:[0-9]+))/
+
+float:
+    # based on $RE{num}{real} from Regexp::Common::number
+    /(?:(?i)(?:[+-]?)(?:(?=[0-9]|[.])(?:[0-9]*)(?:(?:[.])(?:[0-9]{0,}))?)(?:(?:[Ee])(?:(?:[+-]?)(?:[0-9]+))|))/
+
+bool:
+    /[tf]\s+?/i
+        { $item[1] =~ /t/i ? 1 : 0 }
+
+string:
+    /\S[^#\n]*/
+
+end_of_line:
+    /[^\n]*/
Index: /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/lib/PS/IPP/Metadata/Config.pod
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/lib/PS/IPP/Metadata/Config.pod	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/lib/PS/IPP/Metadata/Config.pod	(revision 22254)
@@ -0,0 +1,128 @@
+=pod
+
+=head1 NAME
+
+PS::IPP::Metadata::Config - Parses the psMetadata config language
+
+=head1 SYNOPSIS
+
+    use PS::IPP::Metadata::Config;
+
+    my $mdparser = PS::IPP::Metadata::Config->new;
+    $mdparser->overwrite( 1 );
+
+    my $config = $mdparser->parse( $example );
+
+=head1 DESCRIPTION
+
+Parses the psMetadata configuration language into an AoH parse tree.
+
+=head1 USAGE
+
+=head2 Import Parameters
+
+This module accepts no arguments to it's C<import> method and exports no
+I<symbols>.
+
+=head2 Methods
+
+=head3 Constructors
+
+=over 4
+
+=item * new
+
+Accepts no parameters and returns a L<PS::IPP::Metadata::Config> object.
+
+=back
+
+=head3 Object Methods
+
+=over 4
+
+=item * overwrite
+
+Controls the behavior of the parser when encountering keys that have been
+defined more then once.  A true value instructs the parser to discard all but
+the latest declaration and a false value will preserve the first key parsed.
+Accepts 0, 1, or undef.
+
+Defaults to undef.
+
+=item * parse
+
+Accepts a string.  Returns an AoH structure on success or undef on failure.
+
+The format of the returned structure is as follows:
+
+    [
+        {
+            'comment'   => 'This is a comment',
+            'value'     => '123456789',
+            'name'      => 'Bob',
+            'type'      => 'U64',
+            'class'     => 'scalar'
+        },
+        {
+            'comment'   => 'These are prime numbers',
+            'value'     => [
+                            '2',
+                            '3'
+                        ],
+            'name'      => '@primes',
+            'type'      => 'U8',
+            'class'     => 'vector'
+        },
+        ...
+    ]
+
+=back
+
+=head1 DEVELOPER NOTES
+
+=head2 REFERENCES
+
+=over 4
+
+=item PSDC-430-007
+
+Pan-STARRS PS-1 IPP Library Supplementary Design Requirements Specification
+(SDRS): Section 5.3.4, "Configuration Files".
+
+=back
+
+=head1 CREDITS
+
+Just me, myself, and I.
+
+=head1 SUPPORT
+
+Please contact the author directly via e-mail.
+
+=head1 AUTHOR
+
+Joshua Hoblitt <jhoblitt@cpan.org>
+
+=head1 COPYRIGHT
+
+Copyright (C) 2005  Joshua Hoblitt.  All rights reserved.
+
+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; either version 2 of the License, or (at your option) any later
+version.
+
+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
+this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+Place - Suite 330, Boston, MA  02111-1307, USA.
+
+The full text of the license can be found in the LICENSE file included with
+this module, or in the L<perlgpl> Pod as supplied with Perl 5.8.1 and later.
+
+=head1 SEE ALSO
+
+=cut
Index: /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/t/01_load.t
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/t/01_load.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/t/01_load.t	(revision 22254)
@@ -0,0 +1,14 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2005  Joshua Hoblitt
+#
+# $Id: 01_load.t,v 1.1.1.1 2005-03-01 03:38:45 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use lib qw( ./lib );
+
+use Test::More tests => 1;
+
+BEGIN { use_ok( 'PS::IPP::Metadata::Config' ); }
Index: /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/t/02_sdrs_example.t
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/t/02_sdrs_example.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/t/02_sdrs_example.t	(revision 22254)
@@ -0,0 +1,38 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2005  Joshua Hoblitt
+#
+# $Id: 02_sdrs_example.t,v 1.1.1.1 2005-03-01 03:40:20 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use lib qw( ./lib );
+
+use Test::More tests => 1;
+use PS::IPP::Metadata::Config;
+
+my $example = do { local $/; <DATA> };
+
+my $config_parser = PS::IPP::Metadata::Config->new;
+my $pt = $config_parser->parse( $example );
+
+ok( defined( $pt ), "valid parase tree");
+
+__DATA__
+Double     F64     1.23456789      # This is a comment
+Float    F32 0.98765#This is a comment too
+String  STR This is the string that forms the value #comment
+                                                                                                                                          
+ # This is a comment line and is to be ignored
+boolean     BOOL    T # The value of `boolean' is `true'
+                                                                                                                                          
+@primes U8  2,3 5 7,11,13 17 #   These are prime numbers
+                                                                                                                                          
+comment * The rest of this line is ignored, but `comment' is set to be non-unique
+comment STR This
+comment STR     is
+comment STR       a
+comment STR        non-unique
+comment STR                  key
+Float F64 1.23456 # This generates a warning, and, if `overwrite' is `false', is ignored
Index: /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/t/03_gene_example.t
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/t/03_gene_example.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-Metadata-Config/t/03_gene_example.t	(revision 22254)
@@ -0,0 +1,94 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2005  Joshua Hoblitt
+#
+# $Id: 03_gene_example.t,v 1.1.1.1 2005-03-01 03:41:05 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use lib qw( ./lib );
+
+use Test::More tests => 1;
+use PS::IPP::Metadata::Config;
+
+my $example = do { local $/; <DATA> };
+
+my $config_parser = PS::IPP::Metadata::Config->new;
+my $pt = $config_parser->parse( $example );
+
+ok( defined( $pt ), "valid parase tree");
+
+__DATA__
+# these are examples of camera definition variables:
+# skyprobe
+NCELL       S32    1
+NCHIP       S32    1
+#                  FILENAME    EXTNAME  REGION      CHIP      BIASSEC     
+CELL.00     STR    %f00.%x     PHU      [0,0:0,0]   CHIP.00   [0,0:0,0] 
+                   
+# megacam-raw      
+NCELL       S32    72
+NCHIP       S32    36
+#                  FILENAME    EXTNAME  DATASEC     CHIP      BIASSEC     
+CELL.00     STR    %f.%x       AMP00    [0,0:0,0]   CHIP.00   BIASSEC
+CELL.01     STR    %f.%x       AMP01    [0,0:0,0]   CHIP.00   [2100,2110:0,4096]   
+CELL.02     STR    %f.%x       AMP02    [0,0:0,0]   CHIP.01   [0,0:0,0]   
+CELL.03     STR    %f.%x       AMP03    [0,0:0,0]   CHIP.01   [0,0:0,0]   
+
+# megacam-splice
+NCELL       S32    72
+NCHIP       S32    36
+#                  FILENAME    EXTNAME  REGION      CHIP      BIASSEC   TRIMSEC
+CELL.00     STR    %f.%x       CCD00    ASEC-00     CHIP.00   BSEC-00   DSEC-00
+CELL.01     STR    %f.%x       CCD00    ASEC-01     CHIP.00   BSEC-01   DSEC-01
+CELL.02     STR    %f.%x       CCD01    ASEC-00     CHIP.01   BSEC-00   DSEC-00
+CELL.03     STR    %f.%x       CCD01    ASEC-01     CHIP.01   BSEC-01   DSEC-01
+
+# cfh12k-split
+NCELL       S32    12
+NCHIP       S32    12
+#                  FILENAME    EXTNAME  REGION      CHIP      BIASSEC     
+CELL.00     STR    %f/%f00.%x  PHU      [0,0:0,0]   CHIP.00   [0,0:0,0]   
+CELL.01     STR    %f/%f01.%x  PHU      [0,0:0,0]   CHIP.01   [0,0:0,0]   
+CELL.02     STR    %f/%f02.%x  PHU      [0,0:0,0]   CHIP.02   [0,0:0,0]   
+
+# cfh12k-mef
+NCELL       S32    12
+NCHIP       S32    12
+#                  FILENAME    EXTNAME  REGION      CHIP      BIASSEC     
+CELL.00     STR    %f.%x       CHIP00   [0,0:0,0]   CHIP.00   [0,0:0,0]   
+CELL.01     STR    %f.%x       CHIP01   [0,0:0,0]   CHIP.01   [0,0:0,0]   
+CELL.02     STR    %f.%x       CHIP02   [0,0:0,0]   CHIP.02   [0,0:0,0]   
+
+#- REGION can be defined by a header keyword in IRAF format or by a explicit IRAF format 
+#- what is default for NAXIS1,2 for IRAF format?
+#- Nreadout is always NAXIS3?
+
+# recipe file:
+# this makes the assumption that, for a given camera, all chips &
+# cells have the same recipe.  this is probably a good start, but may
+# not cut it in general. eg, it is already clear that for 
+
+# recipe file must be a function of time and camera.
+# 
+
+# BIAS:
+BIAS.IMAGE                 STR    NONE
+BIAS.IMAGE  		   STR    FILE:bias.fits
+BIAS.IMAGE  		   STR    DB:BEST
+BIAS.IMAGE  		   STR    DB:CLOSE
+
+BIAS.OVERSCAN 		   STR    HEADER:BIASSEC
+BIAS.OVERSCAN 		   STR    RECIPE:[0,0:0,0]
+BIAS.OVERSCAN 		   STR    NONE
+
+BIAS.OVERSCAN.STATS 	   STR    MEDIAN
+BIAS.OVERSCAN.STATS 	   STR    MEAN
+
+BIAS.OVERSCAN.FIT          STR    SPLINE
+BIAS.OVERSCAN.FIT.NPTS     S32    5
+
+BIAS.OVERSCAN.FIT          STR    POLYNOMIAL
+BIAS.OVERSCAN.FIT.ORDER    S32    3
+BIAS.OVERSCAN.FIT.NBIN     S32    5
Index: /branches/jh_branches/jhoblitt/PS-IPP-PSFTP/.cvsignore
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-PSFTP/.cvsignore	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-PSFTP/.cvsignore	(revision 22254)
@@ -0,0 +1,6 @@
+Build
+META.yml
+Makefile
+Makefile.PL
+_build
+blib
Index: /branches/jh_branches/jhoblitt/PS-IPP-PSFTP/Build.PL
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-PSFTP/Build.PL	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-PSFTP/Build.PL	(revision 22254)
@@ -0,0 +1,13 @@
+use Module::Build;
+# See perldoc Module::Build for details of how this works
+
+Module::Build->new(
+    module_name         => 'PS::IPP::PSFTP',
+    dist_version_from   => 'lib/PS/IPP/PSFTP.pm',
+    author              => 'Joshua Hoblitt <jhoblitt@cpan.org>',
+    license             => 'gpl',
+    create_makefile_pl  => 'passthrough',
+    recommends      => {
+        'Test::Distribution' => 0,
+    },
+)->create_build_script;
Index: /branches/jh_branches/jhoblitt/PS-IPP-PSFTP/Changes
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-PSFTP/Changes	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-PSFTP/Changes	(revision 22254)
@@ -0,0 +1,6 @@
+Revision history for Perl module PS::IPP::PSFTP
+
+0.01 Wed Oct  5 14:00:26 2005
+    - original version; created by ExtUtils::ModuleMaker 0.41
+
+
Index: /branches/jh_branches/jhoblitt/PS-IPP-PSFTP/LICENSE
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-PSFTP/LICENSE	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-PSFTP/LICENSE	(revision 22254)
@@ -0,0 +1,260 @@
+The General Public License (GPL)
+Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave,
+Cambridge, MA 02139, USA. Everyone is permitted to copy and distribute
+verbatim copies of this license document, but changing it is not allowed.
+
+Preamble
+
+The licenses for most software are designed to take away your freedom to share
+and change it. By contrast, the GNU General Public License is intended to
+guarantee your freedom to share and change free software--to make sure the
+software is free for all its users. This General Public License applies to most of
+the Free Software Foundation's software and to any other program whose
+authors commit to using it. (Some other Free Software Foundation software is
+covered by the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+When we speak of free software, we are referring to freedom, not price. Our
+General Public Licenses are designed to make sure that you have the freedom
+to distribute copies of free software (and charge for this service if you wish), that
+you receive source code or can get it if you want it, that you can change the
+software or use pieces of it in new free programs; and that you know you can do
+these things.
+
+To protect your rights, we need to make restrictions that forbid anyone to deny
+you these rights or to ask you to surrender the rights. These restrictions
+translate to certain responsibilities for you if you distribute copies of the
+software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether gratis or for a
+fee, you must give the recipients all the rights that you have. You must make
+sure that they, too, receive or can get the source code. And you must show
+them these terms so they know their rights.
+
+We protect your rights with two steps: (1) copyright the software, and (2) offer
+you this license which gives you legal permission to copy, distribute and/or
+modify the software.
+
+Also, for each author's protection and ours, we want to make certain that
+everyone understands that there is no warranty for this free software. If the
+software is modified by someone else and passed on, we want its recipients to
+know that what they have is not the original, so that any problems introduced by
+others will not reflect on the original authors' reputations.
+
+Finally, any free program is threatened constantly by software patents. We wish
+to avoid the danger that redistributors of a free program will individually obtain
+patent licenses, in effect making the program proprietary. To prevent this, we
+have made it clear that any patent must be licensed for everyone's free use or
+not licensed at all.
+
+The precise terms and conditions for copying, distribution and modification
+follow.
+
+GNU GENERAL PUBLIC LICENSE
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND
+MODIFICATION
+
+0. This License applies to any program or other work which contains a notice
+placed by the copyright holder saying it may be distributed under the terms of
+this General Public License. The "Program", below, refers to any such program
+or work, and a "work based on the Program" means either the Program or any
+derivative work under copyright law: that is to say, a work containing the
+Program or a portion of it, either verbatim or with modifications and/or translated
+into another language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not covered by
+this License; they are outside its scope. The act of running the Program is not
+restricted, and the output from the Program is covered only if its contents
+constitute a work based on the Program (independent of having been made by
+running the Program). Whether that is true depends on what the Program does.
+
+1. You may copy and distribute verbatim copies of the Program's source code as
+you receive it, in any medium, provided that you conspicuously and appropriately
+publish on each copy an appropriate copyright notice and disclaimer of warranty;
+keep intact all the notices that refer to this License and to the absence of any
+warranty; and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and you may at
+your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Program or any portion of it, thus
+forming a work based on the Program, and copy and distribute such
+modifications or work under the terms of Section 1 above, provided that you also
+meet all of these conditions:
+
+a) You must cause the modified files to carry prominent notices stating that you
+changed the files and the date of any change.
+
+b) You must cause any work that you distribute or publish, that in whole or in
+part contains or is derived from the Program or any part thereof, to be licensed
+as a whole at no charge to all third parties under the terms of this License.
+
+c) If the modified program normally reads commands interactively when run, you
+must cause it, when started running for such interactive use in the most ordinary
+way, to print or display an announcement including an appropriate copyright
+notice and a notice that there is no warranty (or else, saying that you provide a
+warranty) and that users may redistribute the program under these conditions,
+and telling the user how to view a copy of this License. (Exception: if the
+Program itself is interactive but does not normally print such an announcement,
+your work based on the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If identifiable
+sections of that work are not derived from the Program, and can be reasonably
+considered independent and separate works in themselves, then this License,
+and its terms, do not apply to those sections when you distribute them as
+separate works. But when you distribute the same sections as part of a whole
+which is a work based on the Program, the distribution of the whole must be on
+the terms of this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest your rights to
+work written entirely by you; rather, the intent is to exercise the right to control
+the distribution of derivative or collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program with the
+Program (or with a work based on the Program) on a volume of a storage or
+distribution medium does not bring the other work under the scope of this
+License.
+
+3. You may copy and distribute the Program (or a work based on it, under
+Section 2) in object code or executable form under the terms of Sections 1 and 2
+above provided that you also do one of the following:
+
+a) Accompany it with the complete corresponding machine-readable source
+code, which must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange; or,
+
+b) Accompany it with a written offer, valid for at least three years, to give any
+third party, for a charge no more than your cost of physically performing source
+distribution, a complete machine-readable copy of the corresponding source
+code, to be distributed under the terms of Sections 1 and 2 above on a medium
+customarily used for software interchange; or,
+
+c) Accompany it with the information you received as to the offer to distribute
+corresponding source code. (This alternative is allowed only for noncommercial
+distribution and only if you received the program in object code or executable
+form with such an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for making
+modifications to it. For an executable work, complete source code means all the
+source code for all modules it contains, plus any associated interface definition
+files, plus the scripts used to control compilation and installation of the
+executable. However, as a special exception, the source code distributed need
+not include anything that is normally distributed (in either source or binary form)
+with the major components (compiler, kernel, and so on) of the operating system
+on which the executable runs, unless that component itself accompanies the
+executable.
+
+If distribution of executable or object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the source
+code from the same place counts as distribution of the source code, even though
+third parties are not compelled to copy the source along with the object code.
+
+4. You may not copy, modify, sublicense, or distribute the Program except as
+expressly provided under this License. Any attempt otherwise to copy, modify,
+sublicense or distribute the Program is void, and will automatically terminate
+your rights under this License. However, parties who have received copies, or
+rights, from you under this License will not have their licenses terminated so long
+as such parties remain in full compliance.
+
+5. You are not required to accept this License, since you have not signed it.
+However, nothing else grants you permission to modify or distribute the Program
+or its derivative works. These actions are prohibited by law if you do not accept
+this License. Therefore, by modifying or distributing the Program (or any work
+based on the Program), you indicate your acceptance of this License to do so,
+and all its terms and conditions for copying, distributing or modifying the
+Program or works based on it.
+
+6. Each time you redistribute the Program (or any work based on the Program),
+the recipient automatically receives a license from the original licensor to copy,
+distribute or modify the Program subject to these terms and conditions. You
+may not impose any further restrictions on the recipients' exercise of the rights
+granted herein. You are not responsible for enforcing compliance by third parties
+to this License.
+
+7. If, as a consequence of a court judgment or allegation of patent infringement
+or for any other reason (not limited to patent issues), conditions are imposed on
+you (whether by court order, agreement or otherwise) that contradict the
+conditions of this License, they do not excuse you from the conditions of this
+License. If you cannot distribute so as to satisfy simultaneously your obligations
+under this License and any other pertinent obligations, then as a consequence
+you may not distribute the Program at all. For example, if a patent license would
+not permit royalty-free redistribution of the Program by all those who receive
+copies directly or indirectly through you, then the only way you could satisfy
+both it and this License would be to refrain entirely from distribution of the
+Program.
+
+If any portion of this section is held invalid or unenforceable under any particular
+circumstance, the balance of the section is intended to apply and the section as
+a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any patents or other
+property right claims or to contest validity of any such claims; this section has
+the sole purpose of protecting the integrity of the free software distribution
+system, which is implemented by public license practices. Many people have
+made generous contributions to the wide range of software distributed through
+that system in reliance on consistent application of that system; it is up to the
+author/donor to decide if he or she is willing to distribute software through any
+other system and a licensee cannot impose that choice.
+
+This section is intended to make thoroughly clear what is believed to be a
+consequence of the rest of this License.
+
+8. If the distribution and/or use of the Program is restricted in certain countries
+either by patents or by copyrighted interfaces, the original copyright holder who
+places the Program under this License may add an explicit geographical
+distribution limitation excluding those countries, so that distribution is permitted
+only in or among countries not thus excluded. In such case, this License
+incorporates the limitation as if written in the body of this License.
+
+9. The Free Software Foundation may publish revised and/or new versions of the
+General Public License from time to time. Such new versions will be similar in
+spirit to the present version, but may differ in detail to address new problems or
+concerns.
+
+Each version is given a distinguishing version number. If the Program specifies a
+version number of this License which applies to it and "any later version", you
+have the option of following the terms and conditions either of that version or of
+any later version published by the Free Software Foundation. If the Program does
+not specify a version number of this License, you may choose any version ever
+published by the Free Software Foundation.
+
+10. If you wish to incorporate parts of the Program into other free programs
+whose distribution conditions are different, write to the author to ask for
+permission. For software which is copyrighted by the Free Software Foundation,
+write to the Free Software Foundation; we sometimes make exceptions for this.
+Our decision will be guided by the two goals of preserving the free status of all
+derivatives of our free software and of promoting the sharing and reuse of
+software generally.
+
+NO WARRANTY
+
+11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS
+NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE
+COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM
+"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR
+IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
+ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE,
+YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
+CORRECTION.
+
+12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED
+TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY
+WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS
+PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
+ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM
+(INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY
+OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS
+BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+END OF TERMS AND CONDITIONS
Index: /branches/jh_branches/jhoblitt/PS-IPP-PSFTP/MANIFEST
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-PSFTP/MANIFEST	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-PSFTP/MANIFEST	(revision 22254)
@@ -0,0 +1,10 @@
+Build.PL
+Changes
+LICENSE
+MANIFEST
+META.yml
+README
+Todo
+lib/PS/IPP/PSFTP.pm
+t/00_distribution.t
+t/01_load.t
Index: /branches/jh_branches/jhoblitt/PS-IPP-PSFTP/README
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-PSFTP/README	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-PSFTP/README	(revision 22254)
@@ -0,0 +1,15 @@
+pod2text PS::IPP::PSFTP.pm > README
+
+If this is still here it means the programmer was too lazy to create the readme file.
+
+You can create it now by using the command shown above from this directory.
+
+At the very least you should be able to use this set of instructions
+to install the module...
+
+perl Build.PL
+./Build
+./Build test
+./Build install
+
+If you are on a windows box you should use 'nmake' rather than 'make'.
Index: /branches/jh_branches/jhoblitt/PS-IPP-PSFTP/Todo
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-PSFTP/Todo	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-PSFTP/Todo	(revision 22254)
@@ -0,0 +1,5 @@
+TODO list for Perl module PS::IPP::PSFTP
+
+- Nothing yet
+
+
Index: /branches/jh_branches/jhoblitt/PS-IPP-PSFTP/lib/PS/IPP/PSFTP.pm
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-PSFTP/lib/PS/IPP/PSFTP.pm	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-PSFTP/lib/PS/IPP/PSFTP.pm	(revision 22254)
@@ -0,0 +1,111 @@
+package PS::IPP::PSFTP;
+use strict;
+
+BEGIN {
+    use Exporter ();
+    use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
+    $VERSION     = '0.01';
+    @ISA         = qw(Exporter);
+    #Give a hoot don't pollute, do not export more than needed by default
+    @EXPORT      = qw();
+    @EXPORT_OK   = qw();
+    %EXPORT_TAGS = ();
+}
+
+
+#################### subroutine header begin ####################
+
+=head2 sample_function
+
+ Usage     : How to use this function/method
+ Purpose   : What it does
+ Returns   : What it returns
+ Argument  : What it wants to know
+ Throws    : Exceptions and other anomolies
+ Comment   : This is a sample subroutine header.
+           : It is polite to include more pod and fewer comments.
+
+See Also   : 
+
+=cut
+
+#################### subroutine header end ####################
+
+
+sub new
+{
+    my ($class, %parameters) = @_;
+
+    my $self = bless ({}, ref ($class) || $class);
+
+    return $self;
+}
+
+
+#################### main pod documentation begin ###################
+## Below is the stub of documentation for your module. 
+## You better edit it!
+
+
+=head1 NAME
+
+PS::IPP::PSFTP - Module abstract (<= 44 characters) goes here
+
+=head1 SYNOPSIS
+
+  use PS::IPP::PSFTP;
+  blah blah blah
+
+
+=head1 DESCRIPTION
+
+Stub documentation for this module was created by ExtUtils::ModuleMaker.
+It looks like the author of the extension was negligent enough
+to leave the stub unedited.
+
+Blah blah blah.
+
+
+=head1 USAGE
+
+
+
+=head1 BUGS
+
+
+
+=head1 SUPPORT
+
+
+
+=head1 AUTHOR
+
+	A. U. Thor
+	CPAN ID: MODAUTHOR
+	XYZ Corp.
+	a.u.thor@a.galaxy.far.far.away
+	http://a.galaxy.far.far.away/modules
+
+=head1 COPYRIGHT
+
+This program is free software licensed under the...
+
+	The General Public License (GPL)
+	Version 2, June 1991
+
+The full text of the license can be found in the
+LICENSE file included with this module.
+
+
+=head1 SEE ALSO
+
+perl(1).
+
+=cut
+
+#################### main pod documentation end ###################
+
+
+1;
+# The preceding line will help the module return a true value
+
Index: /branches/jh_branches/jhoblitt/PS-IPP-PSFTP/t/00_distribution.t
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-PSFTP/t/00_distribution.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-PSFTP/t/00_distribution.t	(revision 22254)
@@ -0,0 +1,27 @@
+#!/usr/bin/env perl
+
+# Copyright (C) 2005  Joshua Hoblitt
+#
+# $Id: 00_distribution.t,v 1.1.1.1 2005-10-06 00:03:10 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use lib qw( ./lib ./t );
+
+use Test::More;
+
+# example taken from Test::Distribution Pod
+
+BEGIN {
+    eval {
+        require Test::Distribution;
+    };
+    if($@) {
+        plan skip_all => 'Test::Distribution not installed';
+    } else {
+        # pod tests have to be disabled because of the Pod test strings in
+        # Test::Pod::Tidy
+        import Test::Distribution not => qw( pod );
+    }
+}
Index: /branches/jh_branches/jhoblitt/PS-IPP-PSFTP/t/01_load.t
===================================================================
--- /branches/jh_branches/jhoblitt/PS-IPP-PSFTP/t/01_load.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/PS-IPP-PSFTP/t/01_load.t	(revision 22254)
@@ -0,0 +1,12 @@
+# -*- perl -*-
+
+# t/001_load.t - check module loading and create testing directory
+
+use Test::More tests => 2;
+
+BEGIN { use_ok( 'PS::IPP::PSFTP' ); }
+
+my $object = PS::IPP::PSFTP->new ();
+isa_ok ($object, 'PS::IPP::PSFTP');
+
+
Index: /branches/jh_branches/jhoblitt/doc/design/datasys_cost.tex
===================================================================
--- /branches/jh_branches/jhoblitt/doc/design/datasys_cost.tex	(revision 22254)
+++ /branches/jh_branches/jhoblitt/doc/design/datasys_cost.tex	(revision 22254)
@@ -0,0 +1,233 @@
+\documentclass[panstarrs,psreport]{panstarrs}
+%\usepackage{array}
+
+%\documentclass[panstarrs]{panstarrs}
+
+% basic document variables
+\title{Pan-STARRS PS1 Data Systems Cost Estimate}
+\author{Joshua Hoblitt}
+\shorttitle{PS1 Datasys Cost}
+\group{Pan-STARRS Algorithm Group}
+\project{Pan-STARRS Image Processing Pipeline}
+\organization{Institute for Astronomy}
+\version{01}
+\docnumber{PSDC-xxx-xxx}
+% note the use of the docnumber & version number:
+% the complete PSDC document number is given by
+% \thedocnumber-\theversion
+
+\begin{document}
+\maketitle
+
+% -- Revision History --
+% provide explicit values for the old versions
+% use '\theversion' for the current version (set above)
+% use \hline between each table row
+\RevisionsStart
+% version     Date         Description
+01          & 2004.03.08 & First draft \\
+\hline
+\RevisionsEnd
+
+\pagebreak
+\tableofcontents
+
+\pagebreak
+\listoffigures
+
+\pagebreak 
+\pagenumbering{arabic}
+
+\section{Scope}
+
+\subsection{Identification}
+
+This document establishes the estimated cost of the network and computing hardware necessary to support PS1.  Due to the rapid rate of change in the IT industry the part numbers specified in this document may change prior to the actual purchase of equipment but the cost structure is expected to remain stable.
+
+\section{Image Processing Pipeline and Data Storage Equipment}
+
+\subsection{Requirements}
+
+\subsubsection{CPU}
+
+Phase 2 and phase 4 dominate the IPP computational processing needs.  Assuming 3.0GHz CPUs we need 40 CPUs for phase 2 and 30 CPUs for phase 4.
+
+\subsubsection{Storage}
+
+The storage needs for the first year of PS1's operation have been estimated to be between 700-1100TB.  Assuming that 0.5TB SATA disks become available in the next year we will require between 2000-3000 units.
+
+\subsection{Hardware}
+
+A 5u rackmount case has been identified that can support 24 hot-swappable SATA disks, 1 internal disk, and a dual CPU motherboard.  The large data storage capacity requirement is the driver for the number of complete systems we will have to acquire.
+
+We will need between 85 and 132 nodes to support our data requirements.
+
+\begin{center}
+\begin{tabular}{lrrr}
+% \begin{tabular}{>{$}l<{$}>{$}r>{$}r>{$}r}
+Item&Estimated Price&85 nodes&132 nodes\\
+\cline{1-4}
+24 x 0.5TB SATA disks (bulk storage)	&\$7,200 &\$61,200  &\$950,400\\
+1 x 0.5TB SATA disk (OS + software)	&\$300   &\$25,500  &\$39,600\\
+2 x 3.0GHz CPUs				&\$2,000 &\$170,000 &\$264,000\\
+2 x 12-port SATA raid controller	&\$1,500 &\$127,500 &\$198,000\\
+1 x Motherboard				&\$500   &\$42,500  &\$66,000\\
+4 x 1GB RAM DIMM			&\$1,400 &\$119,000 &\$184,800\\
+1 x 5u Rackmount Chassis		&\$2,800 &\$238,000 &\$369,600\\
+\cline{1-4}
+					&$\sim$\$16,000&\$1,360,000&\$2,112,000\\
+\end{tabular}
+\end{center}
+
+\subsubsection{Spares}
+
+\tbd{TBD}
+
+\subsubsection{Maintenance}
+
+\tbd{TBD}
+
+\section{Network Equipment}
+
+\subsection{Requirements}
+
+\subsubsection{Bandwidth}
+
+It has been estimated that we need a throughput of $\sim604Mb/s$ to support PS1 at a 30s cadence.  A OC-12c Packet over SONET connection can provide enough bandwidth.
+
+\subsubsection{Switch ports}
+
+We will needs to support 32 x Summit Pixel Servers and up to 16 x Servers/Workstations at the summit.  The data center switch should be able to support up to 192 computation nodes and control computers.
+
+\subsection{Network Link}
+
+An OC-12c PoS link from the summit of Haleakala to Kihei on Maui will cost \$16,970 per month with a 3-year contract or \$16,460 per month with a 5-year contract.  Setup fees will vary by location and are estimated to be in the range of \$10,000 - \$100,000 per end point (\$20K - \$200K total).  
+
+\subsection{Hardware}
+
+\subsubsection{Data Center Core Switch}
+
+\begin{center}
+Cisco 6513
+\begin{tabular}{lr}
+Item&Estimated Price\\
+\cline{1-2}
+1 x WS-C6513 Cat 6513	&\$15,250\\
+1 x WS-X6K-S720-6408A	&\$33,000\\
+4 x WS-X6548-RJ-45	&\$17,995\\
+1 x WS-C6K-13SLT-FAN2	&\$745\\
+2 x WS-CAC-4000W-US	&\$5,000\\
+\cline{1-2}
+			&$\sim$\$76,990\\
+\end{tabular}
+\end{center}
+
+\subsubsection{Summit Core Switch}
+
+\begin{center}
+Cisco 6513
+\begin{tabular}{lr}
+Item&Estimated Price\\
+\cline{1-2}
+1 x WS-C6513 Cat 6513	&\$15,250\\
+1 x WS-X6K-S720-6408A	&\$33,000\\
+4 x WS-X6548-RJ-45	&\$17,995\\
+1 x WS-C6K-13SLT-FAN2	&\$745\\
+2 x WS-CAC-4000W-US	&\$5,000\\
+\cline{1-2}
+			&$\sim$\$130,975\\
+\end{tabular}
+\end{center}
+
+\subsubsection{Summit Border Router}
+
+\begin{center}
+Cisco 7606
+\begin{tabular}{lr}
+Item&Estimated Price\\
+\cline{1-2}
+7606-SUP720XL-PS	&\$49,500\\
+OSM-2OC12-POS-SI+	&\$60,000\\
+WS-X6548-RJ-45		&\$17,995\\
+\cline{1-2}
+			&$\sim$\$127,495\\
+\end{tabular}
+\end{center}
+
+\subsubsection{Date Center Border Router}
+
+\begin{center}
+Cisco 7606
+\begin{tabular}{lr}
+Item&Estimated Price\\
+\cline{1-2}
+7606-SUP720XL-PS	&\$49,500\\
+OSM-2OC12-POS-SI+	&\$60,000\\
+WS-X6548-RJ-45		&\$17,995\\
+\cline{1-2}
+			&$\sim$\$127,495\\
+\end{tabular}
+\end{center}
+
+\subsubsection{Spares}
+
+\tbd{TBD}
+
+\subsubsection{Maintenance}
+
+\tbd{Cisco SmartNet contracts are typically $\sim5\%$ of the purchase price.}
+
+\section{Moving Object Pipeline Equipment}
+
+\subsection{Requirements}
+
+\tbd{TBD}
+
+\subsection{Hardware}
+
+\tbd{TBD}
+
+\subsubsection{Spares}
+
+\tbd{TBD}
+
+\subsubsection{Maintenance}
+
+\tbd{TBD}
+
+\section{Software}
+
+\subsection{Lustre}
+
+\subsubsection{Maintenance}
+
+\tbd{TBD}
+
+\section{Power Consumption}
+
+\tbd{Total power consumptions is TBD}
+
+\section{Misc}
+
+\subsection{Hardware}
+
+\begin{center}
+\begin{tabular}{lr}
+Item&Estimated Price\\
+\cline{1-2}
+Racks/cabinets	&\$50,000\\
+Cabling		&\$25,000\\
+Tools		&\$10,000\\
+\cline{1-2}
+		&$\sim$\$85,000\\
+\end{tabular}
+\end{center}
+
+\subsection{Electrical Wiring}
+
+Each cabinet will require 4 x 220v/15amp circuits.  \tbd{Material/labor costs are TBD}.
+
+\bibliographystyle{plain}
+\bibliography{panstarrs}
+
+\end{document}
Index: /branches/jh_branches/jhoblitt/glueforge/glueforge.in
===================================================================
--- /branches/jh_branches/jhoblitt/glueforge/glueforge.in	(revision 22254)
+++ /branches/jh_branches/jhoblitt/glueforge/glueforge.in	(revision 22254)
@@ -0,0 +1,159 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2005  Joshua Hoblitt
+#
+# $Id: glueforge.in,v 1.1.1.1 2005-04-01 03:01:18 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use Data::Dumper;
+use PS::IPP::Metadata::Config;
+use Template;
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version );
+use Pod::Usage qw( pod2usage );
+
+my ( $input, $output, $template );
+GetOptions(
+    'input=s'       => \$input,
+    'output=s'      => \$output,
+    'template=s'    => \$template,
+) || pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required option: --input|-i", -exitval => 2 ) unless $input;
+
+$template = "./template" unless $template;
+
+die "file doesn't exist: $input" unless -e $input;
+die "file isn't readable: $input" unless -r $input;
+
+my $config = parse_config( $input );
+
+my %data;
+
+# find table name
+for (my $i = 0; $i < @{$config}; $i++) {
+    my $elem = $config->[$i];
+
+    if ( $elem->{name} eq "table" ) {
+        $data{table_name} = $elem->{value};
+
+        # delete element from array
+        splice @$config, $i, 1;
+
+        last;
+    }
+}
+
+$data{data_type} = $data{table_name} . "Row";
+$data{namespace} = $data{table_name};
+
+my @items;
+
+# setup items
+for (my $i = 0; $i < @{$config}; $i++) {
+    my $elem = $config->[$i];
+
+    my $mdtypes = lookup_type( $elem->{type} );
+    next if ! defined $mdtypes;
+
+    push @items, {
+        name    => $elem->{name},
+        type    => $elem->{type},
+        ctype   => $mdtypes->{ctype},
+        mtype   => $mdtypes->{mtype},
+        comment => $elem->{comment},
+        value   => $elem->{value},
+    };
+}
+
+$data{columns} = \@items;
+
+#my ( $bootstrap, $configure, $m4, $topmake, $srcmake, $makefile, $header, $code );
+
+my %tt = (
+    'bootstrap_sh.tt'   => "$output/bootstrap.sh",
+    'configure_ac.tt'   => "$output/configure.ac",
+    'top_makefile_am.tt'=> "$output/Makefile.am",
+    'src_makefile_am.tt'=> "$output/src/Makefile.am",
+    'header.tt'         => "$output/src/$data{table_name}db.h",
+    'code.tt'           => "$output/src/$data{table_name}db.c",
+);
+
+my $mangler = Template->new({ INCLUDE_PATH => $template });
+
+if ( $output ) {
+    foreach my $t ( keys %tt ) {
+        $mangler->process( $t, \%data, $tt{$t})
+            or die $mangler->error;
+    }
+} else {
+    foreach my $t ( keys %tt ) {
+        $mangler->process( $t, \%data )
+            or die $mangler->error;
+    }
+}
+
+#$mangler->process( "bootstrap_sh.tt", \%data, $bootstrap)
+#    or die $mangler->error;
+#$mangler->process( "configure_ac.tt", \%data, $configure)
+#    or die $mangler->error;
+#$mangler->process( "top_makefile_am.tt", \%data, $topmake)
+#    or die $mangler->error;
+#$mangler->process( "ac_check_mysqlr.tt", \%data, $m4)
+#    or die $mangler->error;
+#$mangler->process( "src_makefile_am.tt", \%data, $srcmake)
+#    or die $mangler->error;
+#$mangler->process( "header.tt", \%data, $header)
+#    or die $mangler->error;
+#$mangler->process( "code.tt", \%data, $code)
+#    or die $mangler->error;
+
+sub parse_config {
+    my $file = shift;
+
+    my $mdparser = PS::IPP::Metadata::Config->new;
+    $mdparser->overwrite( 1 );
+
+    open( my $data, $file ) or die "can't open file: $!";
+    my $example = do { local $/; <$data> };
+    close( $data ) or die "can't close file: $!";
+
+    my $config = $mdparser->parse( $example )
+        or die "error parsing file: $input";
+
+    return $config;
+}
+
+sub lookup_type {
+    my $type = shift;
+
+    my %primitives = map { $_ => 1 } qw( S8 S16 S32 S64 U8 U16 U32 U64 F32 F64 );
+    my %mtypes = map { $_ => "PS_META_$_" } keys %primitives;
+    my %ctypes = map { $_ => "ps$_" } keys %primitives;
+
+    if ( $primitives{$type} ) {
+        return {
+            ctype   => $ctypes{$type},
+            mtype   => $mtypes{$type},
+        };
+    } elsif ( $type =~ /STR|STRING/ ) {
+        return {
+            ctype   => "char*",
+            mtype   => 'PS_META_STR',
+        };
+    } elsif ( $type =~ /BOOL/ ) {
+        return {
+            ctype   => "bool",
+            mtype   => 'PS_META_STR',
+        };
+    } else {
+        return undef;
+    }
+}
+
Index: /branches/jh_branches/jhoblitt/glueforge/templates/psdb/bootstrap_sh.tt
===================================================================
--- /branches/jh_branches/jhoblitt/glueforge/templates/psdb/bootstrap_sh.tt	(revision 22254)
+++ /branches/jh_branches/jhoblitt/glueforge/templates/psdb/bootstrap_sh.tt	(revision 22254)
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+libtoolize --automake --copy \
+&& aclocal \
+&& autoheader \
+&& automake --foreign --add-missing --copy \
+&& autoconf
Index: /branches/jh_branches/jhoblitt/glueforge/templates/psdb/code.tt
===================================================================
--- /branches/jh_branches/jhoblitt/glueforge/templates/psdb/code.tt	(revision 22254)
+++ /branches/jh_branches/jhoblitt/glueforge/templates/psdb/code.tt	(revision 22254)
@@ -0,0 +1,7 @@
+#include "[% table_name %]db.h"
+
+[% INCLUDE init.tt %]
+
+[% INCLUDE create.tt %]
+
+[% INCLUDE insert.tt %]
Index: /branches/jh_branches/jhoblitt/glueforge/templates/psdb/configure_ac.tt
===================================================================
--- /branches/jh_branches/jhoblitt/glueforge/templates/psdb/configure_ac.tt	(revision 22254)
+++ /branches/jh_branches/jhoblitt/glueforge/templates/psdb/configure_ac.tt	(revision 22254)
@@ -0,0 +1,16 @@
+dnl Process this file with autoconf to produce a configure script.
+AC_INIT([[% table_name %]db], [0.0.1], [pan-starrs.ifa.hawaii.edu])
+AM_INIT_AUTOMAKE(AC_PACKAGE_NAME, AC_PACKAGE_VERSION)
+AM_CONFIG_HEADER(src/config.h)
+
+AC_PROG_CC
+AC_PROG_INSTALL
+AC_PROG_LIBTOOL
+AC_SUBST(LIBTOOL_DEPS)
+
+PKG_CHECK_MODULES(PSLIB, pslib >= 0.5.0) 
+
+dnl AC_CHECK_MYSQLR
+dnl AC_SUBST([MYSQL_CFLAGS MYSQL_LIBS])
+
+AC_OUTPUT(Makefile src/Makefile)
Index: /branches/jh_branches/jhoblitt/glueforge/templates/psdb/create.tt
===================================================================
--- /branches/jh_branches/jhoblitt/glueforge/templates/psdb/create.tt	(revision 22254)
+++ /branches/jh_branches/jhoblitt/glueforge/templates/psdb/create.tt	(revision 22254)
@@ -0,0 +1,26 @@
+bool [% table_name %]CreateTable(psDB *dbh) {
+    psMetadata          *md;
+    bool                status;
+
+    md = psMetadataAlloc();
+    psMetadataAdd(md, 0, "index", PS_META_S32, "Key");
+    [%- SET i = 1 %]
+    [%- FOREACH item = columns %]
+        [%- IF item.comment;
+                SET item.comment = "\"$item.comment\"";
+            ELSE;
+                SET item.comment = 'NULL';
+            END;
+        %]
+        [%- IF item.value %]
+    psMetadataAdd(md, [% i %], "[% item.name %]", [% item.mtype %], [% item.comment %], [% item.value %]);
+        [%- ELSE %]
+    psMetadataAdd(md, [% i %], "[% item.name %]", [% item.mtype %], [% item.comment %]);
+        [%- END %]
+        [%- i = i + 1 %]
+    [%- END %]
+    status = psDBCreateTable(dbh, "[% table_name %]", md);
+    psFree(md);
+
+    return status;
+}
Index: /branches/jh_branches/jhoblitt/glueforge/templates/psdb/create_h.tt
===================================================================
--- /branches/jh_branches/jhoblitt/glueforge/templates/psdb/create_h.tt	(revision 22254)
+++ /branches/jh_branches/jhoblitt/glueforge/templates/psdb/create_h.tt	(revision 22254)
@@ -0,0 +1,7 @@
+/** Creates a new [% table_name %] table
+ *
+ * @return true on success
+ */
+bool [% namespace %]CreateTable(
+    psDB            *dbh                ///< Database handle
+);
Index: /branches/jh_branches/jhoblitt/glueforge/templates/psdb/datatype.tt
===================================================================
--- /branches/jh_branches/jhoblitt/glueforge/templates/psdb/datatype.tt	(revision 22254)
+++ /branches/jh_branches/jhoblitt/glueforge/templates/psdb/datatype.tt	(revision 22254)
@@ -0,0 +1,8 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+typedef struct {
+[% SET i = 0 -%]
+[% FOREACH item = columns -%]
+    [% indented("$item.ctype", "$item.name") %];
+[% END -%]
+} [% data_type %];
Index: /branches/jh_branches/jhoblitt/glueforge/templates/psdb/header.tt
===================================================================
--- /branches/jh_branches/jhoblitt/glueforge/templates/psdb/header.tt	(revision 22254)
+++ /branches/jh_branches/jhoblitt/glueforge/templates/psdb/header.tt	(revision 22254)
@@ -0,0 +1,25 @@
+#ifndef [% table_name %]_DB_H
+#define [% table_name %]_DB_H 1
+
+#include <pslib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/// @addtogroup DataBase
+/// @{
+
+[% INCLUDE datatype.tt %]
+[% INCLUDE init_h.tt %]
+[% INCLUDE create_h.tt %]
+[% INCLUDE insert_h.tt %]
+[% INCLUDE pop_h.tt %]
+
+/// @}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // [% table_name %]_DB_H
Index: /branches/jh_branches/jhoblitt/glueforge/templates/psdb/init.tt
===================================================================
--- /branches/jh_branches/jhoblitt/glueforge/templates/psdb/init.tt	(revision 22254)
+++ /branches/jh_branches/jhoblitt/glueforge/templates/psdb/init.tt	(revision 22254)
@@ -0,0 +1,8 @@
+psDB *[% table_name %]Init(const char *host, const char *user, const char *passwd, const char *dbname) {
+
+    psDB            *dbh;
+
+    dbh = psDBInit(host, user, passwd, dbname);
+
+    return dbh;
+}
Index: /branches/jh_branches/jhoblitt/glueforge/templates/psdb/init_h.tt
===================================================================
--- /branches/jh_branches/jhoblitt/glueforge/templates/psdb/init_h.tt	(revision 22254)
+++ /branches/jh_branches/jhoblitt/glueforge/templates/psdb/init_h.tt	(revision 22254)
@@ -0,0 +1,11 @@
+/** Opens a new database connection
+ *
+ *  @return A new psDB object if the database connection is successful or NULL
+ *  on failure.
+ */
+psDB *[% namespace %]Init(
+    const char      *host,              ///< Database server hostname
+    const char      *user,              ///< Database username
+    const char      *passwd,            ///< Database password
+    const char      *dbname             ///< Database namespace
+);
Index: /branches/jh_branches/jhoblitt/glueforge/templates/psdb/insert.tt
===================================================================
--- /branches/jh_branches/jhoblitt/glueforge/templates/psdb/insert.tt	(revision 22254)
+++ /branches/jh_branches/jhoblitt/glueforge/templates/psdb/insert.tt	(revision 22254)
@@ -0,0 +1,28 @@
+bool [% table_name %]Insert(psDB * dbh,
+    [%- SET i = 0 -%]
+    [%- FOREACH item = columns -%]
+        [%- IF i == 0 -%]
+            [%- i = 1 -%]
+ 
+        [%- ELSE -%]
+, 
+        [%- END -%]
+        [%- item.ctype %] [% item.name %]
+        [%- i = i + 1 -%]
+    [%- END -%]
+)
+{
+    psMetadata      *row;
+    bool            status;
+
+    row = psMetadataAlloc();
+    [%- SET i = 0 %]
+    [%- FOREACH item = columns %]
+    psMetadataAdd(row, [% i %], "[% item.name %]", [% item.mtype %], NULL, [% item.name %]);
+        [%- i = i + 1 %]
+    [%- END %]
+    status = psDBInsertOneRow(dbh, "[% table_name %]", row);
+    psFree(row);
+
+    return status;
+}
Index: /branches/jh_branches/jhoblitt/glueforge/templates/psdb/insert_h.tt
===================================================================
--- /branches/jh_branches/jhoblitt/glueforge/templates/psdb/insert_h.tt	(revision 22254)
+++ /branches/jh_branches/jhoblitt/glueforge/templates/psdb/insert_h.tt	(revision 22254)
@@ -0,0 +1,17 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+bool [% namespace %]Insert(
+    psDB            *dbh,               ///< Database handle
+[%- SET i = 0 -%]
+[% FOREACH item = columns -%]
+[% IF i == 0 %][% i = 1 %][% ELSE %],[% END %]
+    [% indented("$item.ctype", "$item.name") -%]
+[% i = i + 1 -%]
+[% END %]
+);
Index: /branches/jh_branches/jhoblitt/glueforge/templates/psdb/makefile.tt
===================================================================
--- /branches/jh_branches/jhoblitt/glueforge/templates/psdb/makefile.tt	(revision 22254)
+++ /branches/jh_branches/jhoblitt/glueforge/templates/psdb/makefile.tt	(revision 22254)
@@ -0,0 +1,19 @@
+SHELL   = /bin/sh
+CFLAGS  = -g -O0 -pipe -fpic -Wall -pedantic -std=c99
+LIBS    = -lpslib
+CFLAGS += `mysql_config --cflags`
+LIBS   += `mysql_config --libs`
+
+
+objects=[% table_name %]db.o
+
+all: lib[% table_name %]db.so
+
+lib[% table_name %]db.so: $(objects)
+	$(CC) $(CFLAGS) -shared -o lib[% table_name %]db.so $(objects) $(LIBS)
+
+$(objects): %.o : %.c
+	$(CC) $(CFLAGS) -o $@ -c $<
+
+clean:
+	$(RM) *.so *.o core test
Index: /branches/jh_branches/jhoblitt/glueforge/templates/psdb/pop_h.tt
===================================================================
--- /branches/jh_branches/jhoblitt/glueforge/templates/psdb/pop_h.tt	(revision 22254)
+++ /branches/jh_branches/jhoblitt/glueforge/templates/psdb/pop_h.tt	(revision 22254)
@@ -0,0 +1,10 @@
+/** Removes the last row from the database and returns it
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+[% data_type %] [% namespace %]Pop(
+    psDB            *dbh                ///< Database handle
+);
Index: /branches/jh_branches/jhoblitt/glueforge/templates/psdb/src_makefile_am.tt
===================================================================
--- /branches/jh_branches/jhoblitt/glueforge/templates/psdb/src_makefile_am.tt	(revision 22254)
+++ /branches/jh_branches/jhoblitt/glueforge/templates/psdb/src_makefile_am.tt	(revision 22254)
@@ -0,0 +1,10 @@
+AM_CFLAGS   = @AM_CFLAGS@
+AM_CFLAGS   += $(PSLIB_CFLAGS)
+AM_LIBS     = $(PSLIB_LIBS)
+
+include_HEADERS=[% table_name %]db.h
+lib_LTLIBRARIES = lib[% table_name %]db.la
+lib[% table_name %]db_la_LDFLAGS = -release @PACKAGE_VERSION@
+lib[% table_name %]db_la_SOURCES =\
+    [% table_name %]db.h\
+    [% table_name %]db.c
Index: /branches/jh_branches/jhoblitt/glueforge/templates/psdb/top_makefile_am.tt
===================================================================
--- /branches/jh_branches/jhoblitt/glueforge/templates/psdb/top_makefile_am.tt	(revision 22254)
+++ /branches/jh_branches/jhoblitt/glueforge/templates/psdb/top_makefile_am.tt	(revision 22254)
@@ -0,0 +1,4 @@
+ACLOCAL_AMFLAGS = -I m4
+
+AM_CFLAGS  = -g -O0 -pipe -fpic -Wall -pedantic -std=c99
+SUBDIRS = src
Index: /branches/jh_branches/jhoblitt/ippdb/Doxyfile.in
===================================================================
--- /branches/jh_branches/jhoblitt/ippdb/Doxyfile.in	(revision 22254)
+++ /branches/jh_branches/jhoblitt/ippdb/Doxyfile.in	(revision 22254)
@@ -0,0 +1,1219 @@
+# Doxyfile 1.4.2
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded 
+# by quotes) that should identify the project.
+
+PROJECT_NAME           = @PACKAGE_NAME@
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. 
+# This could be handy for archiving the generated documentation or 
+# if some version control system is used.
+
+PROJECT_NUMBER         = @PACKAGE_VERSION@
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) 
+# base path where the generated documentation will be put. 
+# If a relative path is entered, it will be relative to the location 
+# where doxygen was started. If left blank the current directory will be used.
+
+# @top_builddir@ doesn't work for some reason
+OUTPUT_DIRECTORY       = @builddir@/docs
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 
+# 4096 sub-directories (in 2 levels) under the output directory of each output 
+# format and will distribute the generated files over these directories. 
+# Enabling this option can be useful when feeding doxygen a huge amount of 
+# source files, where putting all generated files in the same directory would 
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all 
+# documentation generated by doxygen is written. Doxygen will use this 
+# information to generate all constant output in the proper language. 
+# The default language is English, other supported languages are: 
+# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, 
+# Dutch, Finnish, French, German, Greek, Hungarian, Italian, Japanese, 
+# Japanese-en (Japanese with English messages), Korean, Korean-en, Norwegian, 
+# Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, 
+# Swedish, and Ukrainian.
+
+OUTPUT_LANGUAGE        = English
+
+# This tag can be used to specify the encoding used in the generated output. 
+# The encoding is not always determined by the language that is chosen, 
+# but also whether or not the output is meant for Windows or non-Windows users. 
+# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES 
+# forces the Windows encoding (this is the default for the Windows binary), 
+# whereas setting the tag to NO uses a Unix-style encoding (the default for 
+# all platforms other than Windows).
+
+USE_WINDOWS_ENCODING   = NO
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will 
+# include brief member descriptions after the members that are listed in 
+# the file and class documentation (similar to JavaDoc). 
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend 
+# the brief description of a member or function before the detailed description. 
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the 
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator 
+# that is used to form the text in various listings. Each string 
+# in this list, if found as the leading text of the brief description, will be 
+# stripped from the text and the result after processing the whole list, is 
+# used as the annotated text. Otherwise, the brief description is used as-is. 
+# If left blank, the following values are used ("$name" is automatically 
+# replaced with the name of the entity): "The $name class" "The $name widget" 
+# "The $name file" "is" "provides" "specifies" "contains" 
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       = 
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then 
+# Doxygen will generate a detailed section even if there is only a brief 
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all 
+# inherited members of a class in the documentation of that class as if those 
+# members were ordinary class members. Constructors, destructors and assignment 
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full 
+# path before files name in the file list and in the header files. If set 
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag 
+# can be used to strip a user-defined part of the path. Stripping is 
+# only done if one of the specified strings matches the left-hand part of 
+# the path. The tag can be used to show relative paths in the file list. 
+# If left blank the directory from which doxygen is run is used as the 
+# path to strip.
+
+STRIP_FROM_PATH        = 
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of 
+# the path mentioned in the documentation of a class, which tells 
+# the reader which header file to include in order to use a class. 
+# If left blank only the name of the header file containing the class 
+# definition is used. Otherwise one should specify the include paths that 
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    = 
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter 
+# (but less readable) file names. This can be useful is your file systems 
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen 
+# will interpret the first line (until the first dot) of a JavaDoc-style 
+# comment as the brief description. If set to NO, the JavaDoc 
+# comments will behave just like the Qt-style comments (thus requiring an 
+# explicit @brief command for a brief description.
+
+JAVADOC_AUTOBRIEF      = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen 
+# treat a multi-line C++ special comment block (i.e. a block of //! or /// 
+# comments) as a brief description. This used to be the default behaviour. 
+# The new default is to treat a multi-line C++ comment block as a detailed 
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the DETAILS_AT_TOP tag is set to YES then Doxygen 
+# will output the detailed description near the top, like JavaDoc.
+# If set to NO, the detailed description appears after the member 
+# documentation.
+
+DETAILS_AT_TOP         = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented 
+# member inherits the documentation from any documented member that it 
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC 
+# tag is set to YES, then doxygen will reuse the documentation of the first 
+# member in the group (if any) for the other members of the group. By default 
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce 
+# a new page for each member. If set to NO, the documentation of a member will 
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. 
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 4
+
+# This tag can be used to specify a number of aliases that acts 
+# as commands in the documentation. An alias has the form "name=value". 
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to 
+# put the command \sideeffect (or @sideeffect) in the documentation, which 
+# will result in a user-defined paragraph with heading "Side Effects:". 
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                = 
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C 
+# sources only. Doxygen will then generate output that is more tailored for C. 
+# For instance, some of the names that are used will be different. The list 
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java sources 
+# only. Doxygen will then generate output that is more tailored for Java. 
+# For instance, namespaces will be presented as packages, qualified scopes 
+# will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of 
+# the same type (for instance a group of public functions) to be put as a 
+# subgroup of that type (e.g. under the Public Functions section). Set it to 
+# NO to prevent subgrouping. Alternatively, this can be done per class using 
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in 
+# documentation are documented, even if no documentation was available. 
+# Private class members and static file members will be hidden unless 
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class 
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file 
+# will be included in the documentation.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) 
+# defined locally in source files will be included in the documentation. 
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local 
+# methods, which are defined in the implementation section but not in 
+# the interface are included in the documentation. 
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all 
+# undocumented members of documented classes, files or namespaces. 
+# If set to NO (the default) these members will be included in the 
+# various overviews, but no documentation section is generated. 
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all 
+# undocumented classes that are normally visible in the class hierarchy. 
+# If set to NO (the default) these classes will be included in the various 
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all 
+# friend (class|struct|union) declarations. 
+# If set to NO (the default) these declarations will be included in the 
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any 
+# documentation blocks found inside the body of a function. 
+# If set to NO (the default) these blocks will be appended to the 
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation 
+# that is typed after a \internal command is included. If the tag is set 
+# to NO (the default) then the documentation will be excluded. 
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate 
+# file names in lower-case letters. If set to YES upper-case letters are also 
+# allowed. This is useful if you have classes or files whose names only differ 
+# in case and if your file system supports case sensitive file names. Windows 
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen 
+# will show members with their full class and namespace scopes in the 
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen 
+# will put a list of the files that are included by a file in the documentation 
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] 
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen 
+# will sort the (detailed) documentation of file and class members 
+# alphabetically by member name. If set to NO the members will appear in 
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the 
+# brief documentation of file, namespace and class members alphabetically 
+# by member name. If set to NO (the default) the members will appear in 
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be 
+# sorted by fully-qualified names, including namespaces. If set to 
+# NO (the default), the class list will be sorted only by class name, 
+# not including the namespace part. 
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the 
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or 
+# disable (NO) the todo list. This list is created by putting \todo 
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or 
+# disable (NO) the test list. This list is created by putting \test 
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or 
+# disable (NO) the bug list. This list is created by putting \bug 
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or 
+# disable (NO) the deprecated list. This list is created by putting 
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional 
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       = 
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines 
+# the initial value of a variable or define consists of for it to appear in 
+# the documentation. If the initializer consists of more lines than specified 
+# here it will be hidden. Use a value of 0 to hide initializers completely. 
+# The appearance of the initializer of individual variables and defines in the 
+# documentation can be controlled using \showinitializer or \hideinitializer 
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated 
+# at the bottom of the documentation of classes and structs. If set to YES the 
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# If the sources in your project are distributed over multiple directories 
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy 
+# in the documentation.
+
+SHOW_DIRECTORIES       = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that 
+# doxygen should invoke to get the current version for each file (typically from the 
+# version control system). Doxygen will invoke the program by executing (via 
+# popen()) the command <command> <input-file>, where <command> is the value of 
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file 
+# provided by doxygen. Whatever the progam writes to standard output 
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    = 
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated 
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are 
+# generated by doxygen. Possible values are YES and NO. If left blank 
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings 
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will 
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for 
+# potential errors in the documentation, such as not documenting some 
+# parameters in a documented function, or documenting parameters that 
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for 
+# functions that are documented, but have no documentation for their parameters 
+# or return value. If set to NO (the default) doxygen will only warn about 
+# wrong or incomplete parameter documentation, but not about the absence of 
+# documentation.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that 
+# doxygen can produce. The string should contain the $file, $line, and $text 
+# tags, which will be replaced by the file and line number from which the 
+# warning originated and the warning text. Optionally the format may contain 
+# $version, which will be replaced by the version of the file (if it could 
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning 
+# and error messages should be written. If left blank the output is written 
+# to stderr.
+
+WARN_LOGFILE           = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain 
+# documented source files. You may enter file names like "myfile.cpp" or 
+# directories like "/usr/src/myproject". Separate the files or directories 
+# with spaces.
+
+INPUT                  = @top_srcdir@/src
+
+# If the value of the INPUT tag contains directories, you can use the 
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank the following patterns are tested: 
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx 
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm
+
+FILE_PATTERNS          = *.h
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories 
+# should be searched for input files as well. Possible values are YES and NO. 
+# If left blank NO is used.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should 
+# excluded from the INPUT source files. This way you can easily exclude a 
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE                = 
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or 
+# directories that are symbolic links (a Unix filesystem feature) are excluded 
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the 
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude 
+# certain files from those directories.
+
+EXCLUDE_PATTERNS       = 
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or 
+# directories that contain example code fragments that are included (see 
+# the \include command).
+
+EXAMPLE_PATH           = 
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the 
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank all files are included.
+
+EXAMPLE_PATTERNS       = 
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be 
+# searched for input files to be used with the \include or \dontinclude 
+# commands irrespective of the value of the RECURSIVE tag. 
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or 
+# directories that contain image that are included in the documentation (see 
+# the \image command).
+
+IMAGE_PATH             = 
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should 
+# invoke to filter for each input file. Doxygen will invoke the filter program 
+# by executing (via popen()) the command <filter> <input-file>, where <filter> 
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an 
+# input file. Doxygen will then use the output that the filter program writes 
+# to standard output.  If FILTER_PATTERNS is specified, this tag will be 
+# ignored.
+
+INPUT_FILTER           = 
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern 
+# basis.  Doxygen will compare the file name with each pattern and apply the 
+# filter if there is a match.  The filters are a list of the form: 
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further 
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER 
+# is applied to all files.
+
+FILTER_PATTERNS        = 
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using 
+# INPUT_FILTER) will be used to filter the input files when producing source 
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will 
+# be generated. Documented entities will be cross-referenced with these sources. 
+# Note: To get rid of all source code in the generated output, make sure also 
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body 
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct 
+# doxygen to hide any special comment blocks from generated source code 
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES (the default) 
+# then for each documented function all documented 
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES (the default) 
+# then for each documented function all documented entities 
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = YES
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen 
+# will generate a verbatim copy of the header file for each class for 
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index 
+# of all compounds will be generated. Enable this if the project 
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then 
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns 
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all 
+# classes will be put under the same header in the alphabetical index. 
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that 
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will 
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for 
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank 
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard header.
+
+HTML_HEADER            = 
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard footer.
+
+HTML_FOOTER            = 
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading 
+# style sheet that is used by each HTML page. It can be used to 
+# fine-tune the look of the HTML output. If the tag is left blank doxygen 
+# will generate a default style sheet. Note that doxygen will try to copy 
+# the style sheet file to the HTML output directory, so don't put your own 
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        = 
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, 
+# files or namespaces will be aligned in HTML using tables. If set to 
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS     = YES
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files 
+# will be generated that can be used as input for tools like the 
+# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) 
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can 
+# be used to specify the file name of the resulting .chm file. You 
+# can add a path in front of the file if the result should not be 
+# written to the html output directory.
+
+CHM_FILE               = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can 
+# be used to specify the location (absolute path including file name) of 
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run 
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag 
+# controls if a separate .chi index file is generated (YES) or that 
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag 
+# controls whether a binary table of contents is generated (YES) or a 
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members 
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at 
+# top of each HTML page. The value NO (the default) enables the index and 
+# the value YES disables it.
+
+DISABLE_INDEX          = NO
+
+# This tag can be used to set the number of enum values (range [1..20]) 
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be
+# generated containing a tree-like index structure (just like the one that 
+# is generated for HTML Help). For this to work a browser that supports 
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, 
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are 
+# probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW      = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be 
+# used to set the initial width (in pixels) of the frame in which the tree 
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will 
+# generate Latex output.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be 
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to 
+# generate index for LaTeX. If left blank `makeindex' will be used as the 
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact 
+# LaTeX documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used 
+# by the printer. Possible values are: a4, a4wide, letter, legal and 
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX 
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         = 
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for 
+# the generated latex document. The header should contain everything until 
+# the first chapter. If it is left blank doxygen will generate a 
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           = 
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated 
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will 
+# contain links (just like the HTML output) instead of page references 
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = NO
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of 
+# plain latex in the generated Makefile. Set this option to YES to get a 
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. 
+# command to the generated LaTeX files. This will instruct LaTeX to keep 
+# running if errors occur, instead of asking the user for help. 
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not 
+# include the index chapters (such as File Index, Compound Index, etc.) 
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output 
+# The RTF output is optimized for Word 97 and may not look very pretty with 
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact 
+# RTF documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated 
+# will contain hyperlink fields. The RTF file will 
+# contain links (just like the HTML output) instead of page references. 
+# This makes the output suitable for online browsing using WORD or other 
+# programs which support those fields. 
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's 
+# config file, i.e. a series of assignments. You only have to provide 
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    = 
+
+# Set optional variables used in the generation of an rtf document. 
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will 
+# generate man pages
+
+GENERATE_MAN           = YES
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to 
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output, 
+# then it will generate one additional man file for each entity 
+# documented in the real man page(s). These additional files 
+# only source the real man page, but without them the man command 
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will 
+# generate an XML file that captures the structure of 
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_SCHEMA             = 
+
+# The XML_DTD tag can be used to specify an XML DTD, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_DTD                = 
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will 
+# dump the program listings (including syntax highlighting 
+# and cross-referencing information) to the XML output. Note that 
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will 
+# generate an AutoGen Definitions (see autogen.sf.net) file 
+# that captures the structure of the code including all 
+# documentation. Note that this feature is still experimental 
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will 
+# generate a Perl module file that captures the structure of 
+# the code including all documentation. Note that this 
+# feature is still experimental and incomplete at the 
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate 
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able 
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be 
+# nicely formatted so it can be parsed by a human reader.  This is useful 
+# if you want to understand what is going on.  On the other hand, if this 
+# tag is set to NO the size of the Perl module output will be much smaller 
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file 
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. 
+# This is useful so different doxyrules.make files included by the same 
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX = 
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor   
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will 
+# evaluate all C-preprocessor directives found in the sources and include 
+# files.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro 
+# names in the source code. If set to NO (the default) only conditional 
+# compilation will be performed. Macro expansion can be done in a controlled 
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES 
+# then the macro expansion is limited to the macros specified with the 
+# PREDEFINED and EXPAND_AS_PREDEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files 
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that 
+# contain include files that are not input files but should be processed by 
+# the preprocessor.
+
+INCLUDE_PATH           = 
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard 
+# patterns (like *.h and *.hpp) to filter out the header-files in the 
+# directories. If left blank, the patterns specified with FILE_PATTERNS will 
+# be used.
+
+INCLUDE_FILE_PATTERNS  = 
+
+# The PREDEFINED tag can be used to specify one or more macro names that 
+# are defined before the preprocessor is started (similar to the -D option of 
+# gcc). The argument of the tag is a list of macros of the form: name 
+# or name=definition (no spaces). If the definition and the = are 
+# omitted =1 is assumed. To prevent a macro definition from being 
+# undefined via #undef or recursively expanded use the := operator 
+# instead of the = operator.
+
+PREDEFINED             = 
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then 
+# this tag can be used to specify a list of macro names that should be expanded. 
+# The macro definition that is found in the sources will be used. 
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED      = 
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then 
+# doxygen's preprocessor will remove all function-like macros that are alone 
+# on a line, have an all uppercase name, and do not end with a semicolon. Such 
+# function macros are typically used for boiler-plate code, and will confuse 
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references   
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles. 
+# Optionally an initial location of the external documentation 
+# can be added for each tagfile. The format of a tag file without 
+# this location is as follows: 
+#   TAGFILES = file1 file2 ... 
+# Adding location for the tag files is done as follows: 
+#   TAGFILES = file1=loc1 "file2 = loc2" ... 
+# where "loc1" and "loc2" can be relative or absolute paths or 
+# URLs. If a location is present for each tag, the installdox tool 
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen 
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES               = 
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create 
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       = 
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed 
+# in the class index. If set to NO only the inherited external classes 
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed 
+# in the modules index. If set to NO, only the current project's groups will 
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script 
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = @PERL@
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool   
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will 
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base 
+# or super classes. Setting the tag to NO turns the diagrams off. Note that 
+# this option is superseded by the HAVE_DOT option below. This is only a 
+# fallback. It is recommended to install and use dot, since it yields more 
+# powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# If set to YES, the inheritance and collaboration graphs will hide 
+# inheritance and usage relations if the target is undocumented 
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is 
+# available from the path. This tool is part of Graphviz, a graph visualization 
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section 
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = NO
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect inheritance relations. Setting this tag to YES will force the 
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect implementation dependencies (inheritance, containment, and 
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and 
+# collaboration diagrams in a style similar to the OMG's Unified Modeling 
+# Language.
+
+UML_LOOK               = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the 
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT 
+# tags are set to YES then doxygen will generate a graph for each documented 
+# file showing the direct and indirect include dependencies of the file with 
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and 
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each 
+# documented header file showing the documented files that directly or 
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will 
+# generate a call dependency graph for every global function or class method. 
+# Note that enabling this option will significantly increase the time of a run. 
+# So in most cases it will be better to enable call graphs for selected 
+# functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen 
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES 
+# then doxygen will show the dependencies a directory has on other directories 
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images 
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT       = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be 
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               = 
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that 
+# contain dot files that are included in the documentation (see the 
+# \dotfile command).
+
+DOTFILE_DIRS           = 
+
+# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width 
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than 
+# this value, doxygen will try to truncate the graph, so that it fits within 
+# the specified constraint. Beware that most browsers cannot cope with very 
+# large images.
+
+MAX_DOT_GRAPH_WIDTH    = 1024
+
+# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height 
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than 
+# this value, doxygen will try to truncate the graph, so that it fits within 
+# the specified constraint. Beware that most browsers cannot cope with very 
+# large images.
+
+MAX_DOT_GRAPH_HEIGHT   = 1024
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the 
+# graphs generated by dot. A depth value of 3 means that only nodes reachable 
+# from the root by following a path via at most 3 edges will be shown. Nodes 
+# that lay further from the root node will be omitted. Note that setting this 
+# option to 1 or 2 may greatly reduce the computation time needed for large 
+# code bases. Also note that a graph may be further truncated if the graph's 
+# image dimensions are not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH 
+# and MAX_DOT_GRAPH_HEIGHT). If 0 is used for the depth value (the default), 
+# the graph is not depth-constrained.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent 
+# background. This is disabled by default, which results in a white background. 
+# Warning: Depending on the platform used, enabling this option may lead to 
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to 
+# read).
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output 
+# files in one run (i.e. multiple -o and -T options on the command line). This 
+# makes dot run faster, but since only newer versions of dot (>1.8.10) 
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will 
+# generate a legend page explaining the meaning of the various boxes and 
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will 
+# remove the intermediate dot files that are used to generate 
+# the various graphs.
+
+DOT_CLEANUP            = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to the search engine   
+#---------------------------------------------------------------------------
+
+# The SEARCHENGINE tag specifies whether or not a search engine should be 
+# used. If set to NO the values of all tags below this one will be ignored.
+
+SEARCHENGINE           = NO
Index: /branches/jh_branches/jhoblitt/ippdb/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/ippdb/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/ippdb/Makefile.am	(revision 22254)
@@ -0,0 +1,51 @@
+SUBDIRS = src tests
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA= ippdb.pc
+
+EXTRA_DIST = ippdb.pc.in Doxyfile.in
+
+if DOXYGEN
+
+man_MANS = \
+    $(top_builddir)/docs/man/man3/ippdb.3 \
+    $(top_builddir)/docs/man/man3/weatherRow.3 \
+    $(top_builddir)/docs/man/man3/skyp_transparencyRow.3 \
+    $(top_builddir)/docs/man/man3/skyp_absorptionRow.3 \
+    $(top_builddir)/docs/man/man3/skyp_emissionRow.3 \
+    $(top_builddir)/docs/man/man3/dimmRow.3 \
+    $(top_builddir)/docs/man/man3/skyp_irRow.3 \
+    $(top_builddir)/docs/man/man3/domeRow.3 \
+    $(top_builddir)/docs/man/man3/telescopeRow.3 \
+    $(top_builddir)/docs/man/man3/summitExpRow.3 \
+    $(top_builddir)/docs/man/man3/pzPendingExpRow.3 \
+    $(top_builddir)/docs/man/man3/pzPendingImfileRow.3 \
+    $(top_builddir)/docs/man/man3/newExpRow.3 \
+    $(top_builddir)/docs/man/man3/newImfileRow.3 \
+    $(top_builddir)/docs/man/man3/rawDetrendExpRow.3 \
+    $(top_builddir)/docs/man/man3/rawScienceExpRow.3 \
+    $(top_builddir)/docs/man/man3/rawImfileRow.3 \
+    $(top_builddir)/docs/man/man3/p1PendingExpRow.3 \
+    $(top_builddir)/docs/man/man3/p2PendingExpRow.3 \
+    $(top_builddir)/docs/man/man3/p2PendingImfileRow.3 \
+    $(top_builddir)/docs/man/man3/p2DoneExpRow.3 \
+    $(top_builddir)/docs/man/man3/p2DoneImfileRow.3 \
+    $(top_builddir)/docs/man/man3/p3PendingExpRow.3 \
+    $(top_builddir)/docs/man/man3/detRunRow.3 \
+    $(top_builddir)/docs/man/man3/detInputExpRow.3 \
+    $(top_builddir)/docs/man/man3/detProcessedImfileRow.3 \
+    $(top_builddir)/docs/man/man3/detStackedImfileRow.3 \
+    $(top_builddir)/docs/man/man3/detNormalizedImfileRow.3 \
+    $(top_builddir)/docs/man/man3/detMasterFrameRow.3 \
+    $(top_builddir)/docs/man/man3/detMasterImfileRow.3 \
+    $(top_builddir)/docs/man/man3/detResidImfileAnalysisRow.3 \
+    $(top_builddir)/docs/man/man3/detResidExpAnalysisRow.3 
+
+
+docs/man/man3/ippdb.3 docs/man/man3/weatherRow.3 docs/man/man3/skyp_transparencyRow.3 docs/man/man3/skyp_absorptionRow.3 docs/man/man3/skyp_emissionRow.3 docs/man/man3/dimmRow.3 docs/man/man3/skyp_irRow.3 docs/man/man3/domeRow.3 docs/man/man3/telescopeRow.3 docs/man/man3/summitExpRow.3 docs/man/man3/pzPendingExpRow.3 docs/man/man3/pzPendingImfileRow.3 docs/man/man3/newExpRow.3 docs/man/man3/newImfileRow.3 docs/man/man3/rawDetrendExpRow.3 docs/man/man3/rawScienceExpRow.3 docs/man/man3/rawImfileRow.3 docs/man/man3/p1PendingExpRow.3 docs/man/man3/p2PendingExpRow.3 docs/man/man3/p2PendingImfileRow.3 docs/man/man3/p2DoneExpRow.3 docs/man/man3/p2DoneImfileRow.3 docs/man/man3/p3PendingExpRow.3 docs/man/man3/detRunRow.3 docs/man/man3/detInputExpRow.3 docs/man/man3/detProcessedImfileRow.3 docs/man/man3/detStackedImfileRow.3 docs/man/man3/detNormalizedImfileRow.3 docs/man/man3/detMasterFrameRow.3 docs/man/man3/detMasterImfileRow.3 docs/man/man3/detResidImfileAnalysisRow.3 docs/man/man3/detResidExpAnalysisRow.3:
+	$(DOXYGEN)
+
+endif
+
+clean-local:
+	-rm -rf docs
Index: /branches/jh_branches/jhoblitt/ippdb/autogen.sh
===================================================================
--- /branches/jh_branches/jhoblitt/ippdb/autogen.sh	(revision 22254)
+++ /branches/jh_branches/jhoblitt/ippdb/autogen.sh	(revision 22254)
@@ -0,0 +1,103 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+
+ORIGDIR=`pwd`
+cd $srcdir
+
+PROJECT=ippdb
+TEST_TYPE=-f
+# change this to be a unique filename in the top level dir
+FILE=autogen.sh
+
+DIE=0
+
+LIBTOOLIZE=libtoolize
+ACLOCAL=aclocal
+AUTOHEADER=autoheader
+AUTOMAKE=automake
+AUTOCONF=autoconf
+
+($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $LIBTOOlIZE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/libtool/"
+        DIE=1
+}
+
+($ACLOCAL --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $ACLOCAL installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOHEADER --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOHEADER installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOMAKE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOCONF installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+if test "$DIE" -eq 1; then
+        exit 1
+fi
+
+test $TEST_TYPE $FILE || {
+        echo "You must run this script in the top-level $PROJECT directory"
+        exit 1
+}
+
+if test -z "$*"; then
+        echo "I am going to run ./configure with no arguments - if you wish "
+        echo "to pass any to it, please specify them on the $0 command line."
+fi
+
+$LIBTOOLIZE --copy --force || echo "$LIBTOOlIZE failed"
+$ACLOCAL || echo "$ACLOCAL failed"
+$AUTOHEADER || echo "$AUTOHEADER failed"
+$AUTOMAKE --add-missing --force-missing --copy || echo "$AUTOMAKE failed"
+$AUTOCONF || echo "$AUTOCONF failed"
+
+cd $ORIGDIR
+
+run_configure=true
+for arg in $*; do
+    case $arg in
+        --no-configure)
+            run_configure=false
+            ;;
+        *)
+            ;;
+    esac
+done
+
+if $run_configure; then
+    $srcdir/configure --enable-maintainer-mode "$@"
+    echo
+    echo "Now type 'make' to compile $PROJECT."
+else
+    echo
+    echo "Now run 'configure' and 'make' to compile $PROJECT."
+fi
Index: /branches/jh_branches/jhoblitt/ippdb/configure.ac
===================================================================
--- /branches/jh_branches/jhoblitt/ippdb/configure.ac	(revision 22254)
+++ /branches/jh_branches/jhoblitt/ippdb/configure.ac	(revision 22254)
@@ -0,0 +1,39 @@
+AC_PREREQ(2.59)
+
+AC_INIT([ippdb], [0.0.13], [pan-starrs.ifa.hawaii.edu])
+AC_CONFIG_SRCDIR([ippdb.pc.in])
+
+AM_INIT_AUTOMAKE([1.6 foreign dist-bzip2])
+AM_CONFIG_HEADER([config.h])
+AM_MAINTAINER_MODE
+
+AC_CONFIG_TESTDIR([tests])
+AC_CONFIG_FILES([tests/Makefile])
+AM_MISSING_PROG([AUTOM4TE], [autom4te])
+
+AC_LANG(C)
+AC_PROG_CC
+AC_PROG_INSTALL
+AC_PROG_LIBTOOL
+
+PKG_CHECK_MODULES([PSLIB], [pslib >= 0.9.0]) 
+
+AC_PATH_PROG([PERL], [perl], [missing])
+if test "$PERL" = "missing" ; then
+  AC_MSG_ERROR([perl is required])
+fi
+
+AC_PATH_PROG([doxygen], [doxygen], [missing])
+AM_CONDITIONAL([DOXYGEN], test "x$doxygen" = "xmissing")
+
+dnl is this the best was to setup recursive CFLAGS?
+ippdb_CFLAGS="-Wall -pedantic -std=c99 -fno-strict-aliasing"
+AC_SUBST([ippdb_CFLAGS])
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+  ippdb.pc
+  Doxyfile
+])
+AC_OUTPUT
Index: /branches/jh_branches/jhoblitt/ippdb/ippdb.pc.in
===================================================================
--- /branches/jh_branches/jhoblitt/ippdb/ippdb.pc.in	(revision 22254)
+++ /branches/jh_branches/jhoblitt/ippdb/ippdb.pc.in	(revision 22254)
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: @PACKAGE@
+Description: some library
+Version: @VERSION@
+Requires: pslib
+Libs: -L${libdir} -l@PACKAGE@
+Cflags: -I${includedir}
Index: /branches/jh_branches/jhoblitt/ippdb/src/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/ippdb/src/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/ippdb/src/Makefile.am	(revision 22254)
@@ -0,0 +1,10 @@
+AM_CFLAGS = @ippdb_CFLAGS@
+
+include_HEADERS = ippdb.h
+lib_LTLIBRARIES = libippdb.la
+libippdb_la_CFLAGS  = $(PSLIB_CFLAGS) $(AM_CFLAGS)
+libippdb_la_LIBS    = $(PSLIB_LIBS) $(AM_LIBS)
+libippdb_la_LDFLAGS = -release $(PACKAGE_VERSION)
+libippdb_la_SOURCES = \
+    ippdb.h \
+    ippdb.c
Index: /branches/jh_branches/jhoblitt/ippdb/src/ippdb.c
===================================================================
--- /branches/jh_branches/jhoblitt/ippdb/src/ippdb.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/ippdb/src/ippdb.c	(revision 22254)
@@ -0,0 +1,15643 @@
+#include "ippdb.h"
+
+#define WEATHER_TABLE_NAME "weather"
+#define WEATHER_INDEX_NAME "position"
+#define SKYP_TRANSPARENCY_TABLE_NAME "skyp_transparency"
+#define SKYP_TRANSPARENCY_INDEX_NAME "position"
+#define SKYP_ABSORPTION_TABLE_NAME "skyp_absorption"
+#define SKYP_ABSORPTION_INDEX_NAME "position"
+#define SKYP_EMISSION_TABLE_NAME "skyp_emission"
+#define SKYP_EMISSION_INDEX_NAME "position"
+#define DIMM_TABLE_NAME "dimm"
+#define DIMM_INDEX_NAME "position"
+#define SKYP_IR_TABLE_NAME "skyp_ir"
+#define SKYP_IR_INDEX_NAME "position"
+#define DOME_TABLE_NAME "dome"
+#define DOME_INDEX_NAME "position"
+#define TELESCOPE_TABLE_NAME "telescope"
+#define TELESCOPE_INDEX_NAME "position"
+#define SUMMITEXP_TABLE_NAME "summitExp"
+#define SUMMITEXP_INDEX_NAME "position"
+#define PZPENDINGEXP_TABLE_NAME "pzPendingExp"
+#define PZPENDINGEXP_INDEX_NAME "position"
+#define PZPENDINGIMFILE_TABLE_NAME "pzPendingImfile"
+#define PZPENDINGIMFILE_INDEX_NAME "position"
+#define NEWEXP_TABLE_NAME "newExp"
+#define NEWEXP_INDEX_NAME "position"
+#define NEWIMFILE_TABLE_NAME "newImfile"
+#define NEWIMFILE_INDEX_NAME "position"
+#define RAWDETRENDEXP_TABLE_NAME "rawDetrendExp"
+#define RAWDETRENDEXP_INDEX_NAME "position"
+#define RAWSCIENCEEXP_TABLE_NAME "rawScienceExp"
+#define RAWSCIENCEEXP_INDEX_NAME "position"
+#define RAWIMFILE_TABLE_NAME "rawImfile"
+#define RAWIMFILE_INDEX_NAME "position"
+#define P1PENDINGEXP_TABLE_NAME "p1PendingExp"
+#define P1PENDINGEXP_INDEX_NAME "position"
+#define P2PENDINGEXP_TABLE_NAME "p2PendingExp"
+#define P2PENDINGEXP_INDEX_NAME "position"
+#define P2PENDINGIMFILE_TABLE_NAME "p2PendingImfile"
+#define P2PENDINGIMFILE_INDEX_NAME "position"
+#define P2DONEEXP_TABLE_NAME "p2DoneExp"
+#define P2DONEEXP_INDEX_NAME "position"
+#define P2DONEIMFILE_TABLE_NAME "p2DoneImfile"
+#define P2DONEIMFILE_INDEX_NAME "position"
+#define P3PENDINGEXP_TABLE_NAME "p3PendingExp"
+#define P3PENDINGEXP_INDEX_NAME "position"
+#define DETRUN_TABLE_NAME "detRun"
+#define DETRUN_INDEX_NAME "position"
+#define DETINPUTEXP_TABLE_NAME "detInputExp"
+#define DETINPUTEXP_INDEX_NAME "position"
+#define DETPROCESSEDIMFILE_TABLE_NAME "detProcessedImfile"
+#define DETPROCESSEDIMFILE_INDEX_NAME "position"
+#define DETSTACKEDIMFILE_TABLE_NAME "detStackedImfile"
+#define DETSTACKEDIMFILE_INDEX_NAME "position"
+#define DETNORMALIZEDIMFILE_TABLE_NAME "detNormalizedImfile"
+#define DETNORMALIZEDIMFILE_INDEX_NAME "position"
+#define DETMASTERFRAME_TABLE_NAME "detMasterFrame"
+#define DETMASTERFRAME_INDEX_NAME "position"
+#define DETMASTERIMFILE_TABLE_NAME "detMasterImfile"
+#define DETMASTERIMFILE_INDEX_NAME "position"
+#define DETRESIDIMFILEANALYSIS_TABLE_NAME "detResidImfileAnalysis"
+#define DETRESIDIMFILEANALYSIS_INDEX_NAME "position"
+#define DETRESIDEXPANALYSIS_TABLE_NAME "detResidExpAnalysis"
+#define DETRESIDEXPANALYSIS_INDEX_NAME "position"
+#define MAX_STRING_LENGTH 1024
+
+psDB *ippdbInit(const char *host, const char *user, const char *passwd, const char *dbname)
+{
+    return psDBInit(host, user, passwd, dbname);
+}
+
+void ippdbCleanup(psDB *dbh)
+{
+    psDBCleanup(dbh);
+}
+
+static void weatherRowFree(weatherRow *object);
+
+weatherRow *weatherRowAlloc(psF32 temp01, psF32 humi01, psF32 temp02, psF32 humi02, psF32 temp03, psF32 humi03, psF32 pressure)
+{
+    weatherRow      *object;
+
+    object = psAlloc(sizeof(weatherRow));
+    psMemSetDeallocator(object, (psFreeFunc)weatherRowFree);
+
+    object->temp01 = temp01;
+    object->humi01 = humi01;
+    object->temp02 = temp02;
+    object->humi02 = humi02;
+    object->temp03 = temp03;
+    object->humi03 = humi03;
+    object->pressure = pressure;
+
+    return object;
+}
+
+static void weatherRowFree(weatherRow *object)
+{
+}
+
+bool weatherCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, WEATHER_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", WEATHER_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "temp01", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item temp01");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "humi01", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item humi01");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "temp02", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item temp02");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "humi02", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item humi02");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "temp03", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item temp03");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "humi03", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item humi03");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "pressure", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item pressure");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, WEATHER_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool weatherDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, WEATHER_TABLE_NAME);
+}
+
+bool weatherInsert(psDB * dbh, psF32 temp01, psF32 humi01, psF32 temp02, psF32 humi02, psF32 temp03, psF32 humi03, psF32 pressure)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "temp01", 0, NULL, temp01)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item temp01");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "humi01", 0, NULL, humi01)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item humi01");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "temp02", 0, NULL, temp02)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item temp02");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "humi02", 0, NULL, humi02)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item humi02");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "temp03", 0, NULL, temp03)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item temp03");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "humi03", 0, NULL, humi03)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item humi03");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "pressure", 0, NULL, pressure)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item pressure");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, WEATHER_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long weatherDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, WEATHER_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from weather");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool weatherPop(psDB *dbh, psF32 *temp01, psF32 *humi01, psF32 *temp02, psF32 *humi02, psF32 *temp03, psF32 *humi03, psF32 *pressure)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, WEATHER_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", WEATHER_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, WEATHER_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", WEATHER_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, WEATHER_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, WEATHER_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);
+
+    *temp01 = psMetadataLookupF32(&status, row, "temp01");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item temp01");
+        psFree(row);
+        return false;
+    }
+    *humi01 = psMetadataLookupF32(&status, row, "humi01");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item humi01");
+        psFree(row);
+        return false;
+    }
+    *temp02 = psMetadataLookupF32(&status, row, "temp02");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item temp02");
+        psFree(row);
+        return false;
+    }
+    *humi02 = psMetadataLookupF32(&status, row, "humi02");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item humi02");
+        psFree(row);
+        return false;
+    }
+    *temp03 = psMetadataLookupF32(&status, row, "temp03");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item temp03");
+        psFree(row);
+        return false;
+    }
+    *humi03 = psMetadataLookupF32(&status, row, "humi03");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item humi03");
+        psFree(row);
+        return false;
+    }
+    *pressure = psMetadataLookupF32(&status, row, "pressure");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item pressure");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool weatherInsertObject(psDB *dbh, weatherRow *object)
+{
+    return weatherInsert(dbh, object->temp01, object->humi01, object->temp02, object->humi02, object->temp03, object->humi03, object->pressure);
+}
+
+weatherRow *weatherPopObject(psDB *dbh)
+{
+    psF32           temp01;
+    psF32           humi01;
+    psF32           temp02;
+    psF32           humi02;
+    psF32           temp03;
+    psF32           humi03;
+    psF32           pressure;
+
+    if (!weatherPop(dbh, &temp01, &humi01, &temp02, &humi02, &temp03, &humi03, &pressure)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return weatherRowAlloc(temp01, humi01, temp02, humi02, temp03, humi03, pressure);
+}
+
+bool weatherInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  WEATHER_TABLE_NAME
+    if (!psFitsMoveExtName(fits, WEATHER_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", WEATHER_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, WEATHER_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool weatherPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!weatherSelectRowsFits(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",
+                WEATHER_TABLE_NAME, WEATHER_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 weatherSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, WEATHER_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, WEATHER_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", WEATHER_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, WEATHER_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *weatherMetadataFromObject(const weatherRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "temp01", 0, NULL, object->temp01)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item temp01");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "humi01", 0, NULL, object->humi01)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item humi01");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "temp02", 0, NULL, object->temp02)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item temp02");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "humi02", 0, NULL, object->humi02)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item humi02");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "temp03", 0, NULL, object->temp03)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item temp03");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "humi03", 0, NULL, object->humi03)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item humi03");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "pressure", 0, NULL, object->pressure)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item pressure");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+weatherRow *weatherObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    psF32           temp01;
+    psF32           humi01;
+    psF32           temp02;
+    psF32           humi02;
+    psF32           temp03;
+    psF32           humi03;
+    psF32           pressure;
+
+    temp01 = psMetadataLookupF32(&status, md, "temp01");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item temp01");
+        return false;
+    }
+    humi01 = psMetadataLookupF32(&status, md, "humi01");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item humi01");
+        return false;
+    }
+    temp02 = psMetadataLookupF32(&status, md, "temp02");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item temp02");
+        return false;
+    }
+    humi02 = psMetadataLookupF32(&status, md, "humi02");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item humi02");
+        return false;
+    }
+    temp03 = psMetadataLookupF32(&status, md, "temp03");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item temp03");
+        return false;
+    }
+    humi03 = psMetadataLookupF32(&status, md, "humi03");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item humi03");
+        return false;
+    }
+    pressure = psMetadataLookupF32(&status, md, "pressure");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item pressure");
+        return false;
+    }
+
+    return weatherRowAlloc(temp01, humi01, temp02, humi02, temp03, humi03, pressure);
+}
+psArray *weatherSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, WEATHER_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, WEATHER_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", WEATHER_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++) {
+        weatherRow *object = weatherObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long weatherDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        weatherRow *object = objects->data[i];
+        psMetadata *where = weatherMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, WEATHER_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from weather");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void skyp_transparencyRowFree(skyp_transparencyRow *object);
+
+skyp_transparencyRow *skyp_transparencyRowAlloc(const char *filter, psF64 trans, psS32 nstars, psF64 ra, psF64 decl, psF32 exptime, psF64 sky_bright)
+{
+    skyp_transparencyRow *object;
+
+    object = psAlloc(sizeof(skyp_transparencyRow));
+    psMemSetDeallocator(object, (psFreeFunc)skyp_transparencyRowFree);
+
+    object->filter = psStringCopy(filter);
+    object->trans = trans;
+    object->nstars = nstars;
+    object->ra = ra;
+    object->decl = decl;
+    object->exptime = exptime;
+    object->sky_bright = sky_bright;
+
+    return object;
+}
+
+static void skyp_transparencyRowFree(skyp_transparencyRow *object)
+{
+    psFree(object->filter);
+}
+
+bool skyp_transparencyCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, SKYP_TRANSPARENCY_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", SKYP_TRANSPARENCY_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "trans", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item trans");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "nstars", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item nstars");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exptime", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exptime");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_bright", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sky_bright");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, SKYP_TRANSPARENCY_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool skyp_transparencyDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, SKYP_TRANSPARENCY_TABLE_NAME);
+}
+
+bool skyp_transparencyInsert(psDB * dbh, const char *filter, psF64 trans, psS32 nstars, psF64 ra, psF64 decl, psF32 exptime, psF64 sky_bright)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, filter)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "trans", 0, NULL, trans)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item trans");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "nstars", 0, NULL, nstars)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item nstars");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exptime", 0, NULL, exptime)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exptime");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_bright", 0, NULL, sky_bright)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sky_bright");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, SKYP_TRANSPARENCY_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long skyp_transparencyDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, SKYP_TRANSPARENCY_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from skyp_transparency");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool skyp_transparencyPop(psDB *dbh, char **filter, psF64 *trans, psS32 *nstars, psF64 *ra, psF64 *decl, psF32 *exptime, psF64 *sky_bright)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, SKYP_TRANSPARENCY_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", SKYP_TRANSPARENCY_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, SKYP_TRANSPARENCY_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", SKYP_TRANSPARENCY_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, SKYP_TRANSPARENCY_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, SKYP_TRANSPARENCY_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);
+
+    *filter = psMetadataLookupPtr(&status, row, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        psFree(row);
+        return false;
+    }
+    *trans = psMetadataLookupF64(&status, row, "trans");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item trans");
+        psFree(row);
+        return false;
+    }
+    *nstars = psMetadataLookupS32(&status, row, "nstars");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item nstars");
+        psFree(row);
+        return false;
+    }
+    *ra = psMetadataLookupF64(&status, row, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        psFree(row);
+        return false;
+    }
+    *decl = psMetadataLookupF64(&status, row, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        psFree(row);
+        return false;
+    }
+    *exptime = psMetadataLookupF32(&status, row, "exptime");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exptime");
+        psFree(row);
+        return false;
+    }
+    *sky_bright = psMetadataLookupF64(&status, row, "sky_bright");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sky_bright");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool skyp_transparencyInsertObject(psDB *dbh, skyp_transparencyRow *object)
+{
+    return skyp_transparencyInsert(dbh, object->filter, object->trans, object->nstars, object->ra, object->decl, object->exptime, object->sky_bright);
+}
+
+skyp_transparencyRow *skyp_transparencyPopObject(psDB *dbh)
+{
+    char            filter[256];
+    psF64           trans;
+    psS32           nstars;
+    psF64           ra;
+    psF64           decl;
+    psF32           exptime;
+    psF64           sky_bright;
+
+    if (!skyp_transparencyPop(dbh, (char **)&filter, &trans, &nstars, &ra, &decl, &exptime, &sky_bright)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return skyp_transparencyRowAlloc(filter, trans, nstars, ra, decl, exptime, sky_bright);
+}
+
+bool skyp_transparencyInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  SKYP_TRANSPARENCY_TABLE_NAME
+    if (!psFitsMoveExtName(fits, SKYP_TRANSPARENCY_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", SKYP_TRANSPARENCY_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, SKYP_TRANSPARENCY_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool skyp_transparencyPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!skyp_transparencySelectRowsFits(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",
+                SKYP_TRANSPARENCY_TABLE_NAME, SKYP_TRANSPARENCY_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 skyp_transparencySelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, SKYP_TRANSPARENCY_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, SKYP_TRANSPARENCY_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", SKYP_TRANSPARENCY_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, SKYP_TRANSPARENCY_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *skyp_transparencyMetadataFromObject(const skyp_transparencyRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, object->filter)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "trans", 0, NULL, object->trans)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item trans");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "nstars", 0, NULL, object->nstars)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item nstars");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, object->ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, object->decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exptime", 0, NULL, object->exptime)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exptime");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_bright", 0, NULL, object->sky_bright)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sky_bright");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+skyp_transparencyRow *skyp_transparencyObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *filter;
+    psF64           trans;
+    psS32           nstars;
+    psF64           ra;
+    psF64           decl;
+    psF32           exptime;
+    psF64           sky_bright;
+
+    filter = psMetadataLookupPtr(&status, md, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        return false;
+    }
+    trans = psMetadataLookupF64(&status, md, "trans");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item trans");
+        return false;
+    }
+    nstars = psMetadataLookupS32(&status, md, "nstars");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item nstars");
+        return false;
+    }
+    ra = psMetadataLookupF64(&status, md, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        return false;
+    }
+    decl = psMetadataLookupF64(&status, md, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        return false;
+    }
+    exptime = psMetadataLookupF32(&status, md, "exptime");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exptime");
+        return false;
+    }
+    sky_bright = psMetadataLookupF64(&status, md, "sky_bright");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sky_bright");
+        return false;
+    }
+
+    return skyp_transparencyRowAlloc(filter, trans, nstars, ra, decl, exptime, sky_bright);
+}
+psArray *skyp_transparencySelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, SKYP_TRANSPARENCY_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, SKYP_TRANSPARENCY_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", SKYP_TRANSPARENCY_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++) {
+        skyp_transparencyRow *object = skyp_transparencyObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long skyp_transparencyDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        skyp_transparencyRow *object = objects->data[i];
+        psMetadata *where = skyp_transparencyMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, SKYP_TRANSPARENCY_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from skyp_transparency");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void skyp_absorptionRowFree(skyp_absorptionRow *object);
+
+skyp_absorptionRow *skyp_absorptionRowAlloc(const char *disperser_id, psF32 atmcomp1, psF32 atmcomp2, psF32 atmcomp3, psS32 nstars, psF64 ra, psF64 decl, psF32 exptime, psF64 sky_bright)
+{
+    skyp_absorptionRow *object;
+
+    object = psAlloc(sizeof(skyp_absorptionRow));
+    psMemSetDeallocator(object, (psFreeFunc)skyp_absorptionRowFree);
+
+    object->disperser_id = psStringCopy(disperser_id);
+    object->atmcomp1 = atmcomp1;
+    object->atmcomp2 = atmcomp2;
+    object->atmcomp3 = atmcomp3;
+    object->nstars = nstars;
+    object->ra = ra;
+    object->decl = decl;
+    object->exptime = exptime;
+    object->sky_bright = sky_bright;
+
+    return object;
+}
+
+static void skyp_absorptionRowFree(skyp_absorptionRow *object)
+{
+    psFree(object->disperser_id);
+}
+
+bool skyp_absorptionCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, SKYP_ABSORPTION_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", SKYP_ABSORPTION_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "disperser_id", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item disperser_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp1", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp1");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp2", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp2");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp3", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp3");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "nstars", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item nstars");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exptime", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exptime");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_bright", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sky_bright");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, SKYP_ABSORPTION_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool skyp_absorptionDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, SKYP_ABSORPTION_TABLE_NAME);
+}
+
+bool skyp_absorptionInsert(psDB * dbh, const char *disperser_id, psF32 atmcomp1, psF32 atmcomp2, psF32 atmcomp3, psS32 nstars, psF64 ra, psF64 decl, psF32 exptime, psF64 sky_bright)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "disperser_id", 0, NULL, disperser_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item disperser_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp1", 0, NULL, atmcomp1)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp1");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp2", 0, NULL, atmcomp2)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp2");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp3", 0, NULL, atmcomp3)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp3");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "nstars", 0, NULL, nstars)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item nstars");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exptime", 0, NULL, exptime)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exptime");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_bright", 0, NULL, sky_bright)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sky_bright");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, SKYP_ABSORPTION_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long skyp_absorptionDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, SKYP_ABSORPTION_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from skyp_absorption");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool skyp_absorptionPop(psDB *dbh, char **disperser_id, psF32 *atmcomp1, psF32 *atmcomp2, psF32 *atmcomp3, psS32 *nstars, psF64 *ra, psF64 *decl, psF32 *exptime, psF64 *sky_bright)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, SKYP_ABSORPTION_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", SKYP_ABSORPTION_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, SKYP_ABSORPTION_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", SKYP_ABSORPTION_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, SKYP_ABSORPTION_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, SKYP_ABSORPTION_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);
+
+    *disperser_id = psMetadataLookupPtr(&status, row, "disperser_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item disperser_id");
+        psFree(row);
+        return false;
+    }
+    *atmcomp1 = psMetadataLookupF32(&status, row, "atmcomp1");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item atmcomp1");
+        psFree(row);
+        return false;
+    }
+    *atmcomp2 = psMetadataLookupF32(&status, row, "atmcomp2");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item atmcomp2");
+        psFree(row);
+        return false;
+    }
+    *atmcomp3 = psMetadataLookupF32(&status, row, "atmcomp3");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item atmcomp3");
+        psFree(row);
+        return false;
+    }
+    *nstars = psMetadataLookupS32(&status, row, "nstars");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item nstars");
+        psFree(row);
+        return false;
+    }
+    *ra = psMetadataLookupF64(&status, row, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        psFree(row);
+        return false;
+    }
+    *decl = psMetadataLookupF64(&status, row, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        psFree(row);
+        return false;
+    }
+    *exptime = psMetadataLookupF32(&status, row, "exptime");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exptime");
+        psFree(row);
+        return false;
+    }
+    *sky_bright = psMetadataLookupF64(&status, row, "sky_bright");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sky_bright");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool skyp_absorptionInsertObject(psDB *dbh, skyp_absorptionRow *object)
+{
+    return skyp_absorptionInsert(dbh, object->disperser_id, object->atmcomp1, object->atmcomp2, object->atmcomp3, object->nstars, object->ra, object->decl, object->exptime, object->sky_bright);
+}
+
+skyp_absorptionRow *skyp_absorptionPopObject(psDB *dbh)
+{
+    char            disperser_id[256];
+    psF32           atmcomp1;
+    psF32           atmcomp2;
+    psF32           atmcomp3;
+    psS32           nstars;
+    psF64           ra;
+    psF64           decl;
+    psF32           exptime;
+    psF64           sky_bright;
+
+    if (!skyp_absorptionPop(dbh, (char **)&disperser_id, &atmcomp1, &atmcomp2, &atmcomp3, &nstars, &ra, &decl, &exptime, &sky_bright)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return skyp_absorptionRowAlloc(disperser_id, atmcomp1, atmcomp2, atmcomp3, nstars, ra, decl, exptime, sky_bright);
+}
+
+bool skyp_absorptionInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  SKYP_ABSORPTION_TABLE_NAME
+    if (!psFitsMoveExtName(fits, SKYP_ABSORPTION_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", SKYP_ABSORPTION_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, SKYP_ABSORPTION_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool skyp_absorptionPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!skyp_absorptionSelectRowsFits(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",
+                SKYP_ABSORPTION_TABLE_NAME, SKYP_ABSORPTION_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 skyp_absorptionSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, SKYP_ABSORPTION_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, SKYP_ABSORPTION_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", SKYP_ABSORPTION_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, SKYP_ABSORPTION_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *skyp_absorptionMetadataFromObject(const skyp_absorptionRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "disperser_id", 0, NULL, object->disperser_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item disperser_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp1", 0, NULL, object->atmcomp1)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp1");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp2", 0, NULL, object->atmcomp2)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp2");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp3", 0, NULL, object->atmcomp3)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp3");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "nstars", 0, NULL, object->nstars)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item nstars");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, object->ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, object->decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exptime", 0, NULL, object->exptime)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exptime");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_bright", 0, NULL, object->sky_bright)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sky_bright");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+skyp_absorptionRow *skyp_absorptionObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *disperser_id;
+    psF32           atmcomp1;
+    psF32           atmcomp2;
+    psF32           atmcomp3;
+    psS32           nstars;
+    psF64           ra;
+    psF64           decl;
+    psF32           exptime;
+    psF64           sky_bright;
+
+    disperser_id = psMetadataLookupPtr(&status, md, "disperser_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item disperser_id");
+        return false;
+    }
+    atmcomp1 = psMetadataLookupF32(&status, md, "atmcomp1");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item atmcomp1");
+        return false;
+    }
+    atmcomp2 = psMetadataLookupF32(&status, md, "atmcomp2");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item atmcomp2");
+        return false;
+    }
+    atmcomp3 = psMetadataLookupF32(&status, md, "atmcomp3");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item atmcomp3");
+        return false;
+    }
+    nstars = psMetadataLookupS32(&status, md, "nstars");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item nstars");
+        return false;
+    }
+    ra = psMetadataLookupF64(&status, md, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        return false;
+    }
+    decl = psMetadataLookupF64(&status, md, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        return false;
+    }
+    exptime = psMetadataLookupF32(&status, md, "exptime");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exptime");
+        return false;
+    }
+    sky_bright = psMetadataLookupF64(&status, md, "sky_bright");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sky_bright");
+        return false;
+    }
+
+    return skyp_absorptionRowAlloc(disperser_id, atmcomp1, atmcomp2, atmcomp3, nstars, ra, decl, exptime, sky_bright);
+}
+psArray *skyp_absorptionSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, SKYP_ABSORPTION_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, SKYP_ABSORPTION_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", SKYP_ABSORPTION_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++) {
+        skyp_absorptionRow *object = skyp_absorptionObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long skyp_absorptionDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        skyp_absorptionRow *object = objects->data[i];
+        psMetadata *where = skyp_absorptionMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, SKYP_ABSORPTION_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from skyp_absorption");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void skyp_emissionRowFree(skyp_emissionRow *object);
+
+skyp_emissionRow *skyp_emissionRowAlloc(const char *disperser_id, psF32 atmcomp1, psF32 atmcomp2, psF32 atmcomp3, psF32 continuum, psF32 exptime)
+{
+    skyp_emissionRow *object;
+
+    object = psAlloc(sizeof(skyp_emissionRow));
+    psMemSetDeallocator(object, (psFreeFunc)skyp_emissionRowFree);
+
+    object->disperser_id = psStringCopy(disperser_id);
+    object->atmcomp1 = atmcomp1;
+    object->atmcomp2 = atmcomp2;
+    object->atmcomp3 = atmcomp3;
+    object->continuum = continuum;
+    object->exptime = exptime;
+
+    return object;
+}
+
+static void skyp_emissionRowFree(skyp_emissionRow *object)
+{
+    psFree(object->disperser_id);
+}
+
+bool skyp_emissionCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, SKYP_EMISSION_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", SKYP_EMISSION_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "disperser_id", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item disperser_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp1", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp1");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp2", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp2");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp3", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp3");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "continuum", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item continuum");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exptime", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exptime");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, SKYP_EMISSION_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool skyp_emissionDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, SKYP_EMISSION_TABLE_NAME);
+}
+
+bool skyp_emissionInsert(psDB * dbh, const char *disperser_id, psF32 atmcomp1, psF32 atmcomp2, psF32 atmcomp3, psF32 continuum, psF32 exptime)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "disperser_id", 0, NULL, disperser_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item disperser_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp1", 0, NULL, atmcomp1)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp1");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp2", 0, NULL, atmcomp2)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp2");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp3", 0, NULL, atmcomp3)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp3");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "continuum", 0, NULL, continuum)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item continuum");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exptime", 0, NULL, exptime)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exptime");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, SKYP_EMISSION_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long skyp_emissionDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, SKYP_EMISSION_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from skyp_emission");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool skyp_emissionPop(psDB *dbh, char **disperser_id, psF32 *atmcomp1, psF32 *atmcomp2, psF32 *atmcomp3, psF32 *continuum, psF32 *exptime)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, SKYP_EMISSION_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", SKYP_EMISSION_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, SKYP_EMISSION_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", SKYP_EMISSION_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, SKYP_EMISSION_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, SKYP_EMISSION_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);
+
+    *disperser_id = psMetadataLookupPtr(&status, row, "disperser_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item disperser_id");
+        psFree(row);
+        return false;
+    }
+    *atmcomp1 = psMetadataLookupF32(&status, row, "atmcomp1");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item atmcomp1");
+        psFree(row);
+        return false;
+    }
+    *atmcomp2 = psMetadataLookupF32(&status, row, "atmcomp2");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item atmcomp2");
+        psFree(row);
+        return false;
+    }
+    *atmcomp3 = psMetadataLookupF32(&status, row, "atmcomp3");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item atmcomp3");
+        psFree(row);
+        return false;
+    }
+    *continuum = psMetadataLookupF32(&status, row, "continuum");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item continuum");
+        psFree(row);
+        return false;
+    }
+    *exptime = psMetadataLookupF32(&status, row, "exptime");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exptime");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool skyp_emissionInsertObject(psDB *dbh, skyp_emissionRow *object)
+{
+    return skyp_emissionInsert(dbh, object->disperser_id, object->atmcomp1, object->atmcomp2, object->atmcomp3, object->continuum, object->exptime);
+}
+
+skyp_emissionRow *skyp_emissionPopObject(psDB *dbh)
+{
+    char            disperser_id[256];
+    psF32           atmcomp1;
+    psF32           atmcomp2;
+    psF32           atmcomp3;
+    psF32           continuum;
+    psF32           exptime;
+
+    if (!skyp_emissionPop(dbh, (char **)&disperser_id, &atmcomp1, &atmcomp2, &atmcomp3, &continuum, &exptime)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return skyp_emissionRowAlloc(disperser_id, atmcomp1, atmcomp2, atmcomp3, continuum, exptime);
+}
+
+bool skyp_emissionInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  SKYP_EMISSION_TABLE_NAME
+    if (!psFitsMoveExtName(fits, SKYP_EMISSION_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", SKYP_EMISSION_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, SKYP_EMISSION_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool skyp_emissionPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!skyp_emissionSelectRowsFits(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",
+                SKYP_EMISSION_TABLE_NAME, SKYP_EMISSION_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 skyp_emissionSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, SKYP_EMISSION_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, SKYP_EMISSION_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", SKYP_EMISSION_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, SKYP_EMISSION_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *skyp_emissionMetadataFromObject(const skyp_emissionRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "disperser_id", 0, NULL, object->disperser_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item disperser_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp1", 0, NULL, object->atmcomp1)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp1");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp2", 0, NULL, object->atmcomp2)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp2");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp3", 0, NULL, object->atmcomp3)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item atmcomp3");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "continuum", 0, NULL, object->continuum)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item continuum");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exptime", 0, NULL, object->exptime)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exptime");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+skyp_emissionRow *skyp_emissionObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *disperser_id;
+    psF32           atmcomp1;
+    psF32           atmcomp2;
+    psF32           atmcomp3;
+    psF32           continuum;
+    psF32           exptime;
+
+    disperser_id = psMetadataLookupPtr(&status, md, "disperser_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item disperser_id");
+        return false;
+    }
+    atmcomp1 = psMetadataLookupF32(&status, md, "atmcomp1");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item atmcomp1");
+        return false;
+    }
+    atmcomp2 = psMetadataLookupF32(&status, md, "atmcomp2");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item atmcomp2");
+        return false;
+    }
+    atmcomp3 = psMetadataLookupF32(&status, md, "atmcomp3");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item atmcomp3");
+        return false;
+    }
+    continuum = psMetadataLookupF32(&status, md, "continuum");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item continuum");
+        return false;
+    }
+    exptime = psMetadataLookupF32(&status, md, "exptime");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exptime");
+        return false;
+    }
+
+    return skyp_emissionRowAlloc(disperser_id, atmcomp1, atmcomp2, atmcomp3, continuum, exptime);
+}
+psArray *skyp_emissionSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, SKYP_EMISSION_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, SKYP_EMISSION_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", SKYP_EMISSION_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++) {
+        skyp_emissionRow *object = skyp_emissionObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long skyp_emissionDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        skyp_emissionRow *object = objects->data[i];
+        psMetadata *where = skyp_emissionMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, SKYP_EMISSION_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from skyp_emission");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void dimmRowFree(dimmRow *object);
+
+dimmRow *dimmRowAlloc(psF32 sigmax, psF32 sigmay, psF32 fwhm, psF64 ra, psF64 decl, psF32 expttime, const char *telescope_id)
+{
+    dimmRow         *object;
+
+    object = psAlloc(sizeof(dimmRow));
+    psMemSetDeallocator(object, (psFreeFunc)dimmRowFree);
+
+    object->sigmax = sigmax;
+    object->sigmay = sigmay;
+    object->fwhm = fwhm;
+    object->ra = ra;
+    object->decl = decl;
+    object->expttime = expttime;
+    object->telescope_id = psStringCopy(telescope_id);
+
+    return object;
+}
+
+static void dimmRowFree(dimmRow *object)
+{
+    psFree(object->telescope_id);
+}
+
+bool dimmCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, DIMM_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", DIMM_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "sigmax", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sigmax");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "sigmay", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sigmay");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "fwhm", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fwhm");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "expttime", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item expttime");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope_id", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope_id");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, DIMM_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool dimmDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DIMM_TABLE_NAME);
+}
+
+bool dimmInsert(psDB * dbh, psF32 sigmax, psF32 sigmay, psF32 fwhm, psF64 ra, psF64 decl, psF32 expttime, const char *telescope_id)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "sigmax", 0, NULL, sigmax)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sigmax");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "sigmay", 0, NULL, sigmay)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sigmay");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "fwhm", 0, NULL, fwhm)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fwhm");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "expttime", 0, NULL, expttime)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item expttime");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope_id", 0, NULL, telescope_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope_id");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, DIMM_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long dimmDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, DIMM_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from dimm");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool dimmPop(psDB *dbh, psF32 *sigmax, psF32 *sigmay, psF32 *fwhm, psF64 *ra, psF64 *decl, psF32 *expttime, char **telescope_id)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, DIMM_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DIMM_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, DIMM_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DIMM_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, DIMM_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, DIMM_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);
+
+    *sigmax = psMetadataLookupF32(&status, row, "sigmax");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sigmax");
+        psFree(row);
+        return false;
+    }
+    *sigmay = psMetadataLookupF32(&status, row, "sigmay");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sigmay");
+        psFree(row);
+        return false;
+    }
+    *fwhm = psMetadataLookupF32(&status, row, "fwhm");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fwhm");
+        psFree(row);
+        return false;
+    }
+    *ra = psMetadataLookupF64(&status, row, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        psFree(row);
+        return false;
+    }
+    *decl = psMetadataLookupF64(&status, row, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        psFree(row);
+        return false;
+    }
+    *expttime = psMetadataLookupF32(&status, row, "expttime");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item expttime");
+        psFree(row);
+        return false;
+    }
+    *telescope_id = psMetadataLookupPtr(&status, row, "telescope_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope_id");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool dimmInsertObject(psDB *dbh, dimmRow *object)
+{
+    return dimmInsert(dbh, object->sigmax, object->sigmay, object->fwhm, object->ra, object->decl, object->expttime, object->telescope_id);
+}
+
+dimmRow *dimmPopObject(psDB *dbh)
+{
+    psF32           sigmax;
+    psF32           sigmay;
+    psF32           fwhm;
+    psF64           ra;
+    psF64           decl;
+    psF32           expttime;
+    char            telescope_id[256];
+
+    if (!dimmPop(dbh, &sigmax, &sigmay, &fwhm, &ra, &decl, &expttime, (char **)&telescope_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return dimmRowAlloc(sigmax, sigmay, fwhm, ra, decl, expttime, telescope_id);
+}
+
+bool dimmInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DIMM_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DIMM_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DIMM_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, DIMM_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool dimmPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!dimmSelectRowsFits(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",
+                DIMM_TABLE_NAME, DIMM_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 dimmSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DIMM_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, DIMM_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DIMM_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DIMM_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *dimmMetadataFromObject(const dimmRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "sigmax", 0, NULL, object->sigmax)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sigmax");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "sigmay", 0, NULL, object->sigmay)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sigmay");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "fwhm", 0, NULL, object->fwhm)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fwhm");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, object->ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, object->decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "expttime", 0, NULL, object->expttime)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item expttime");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope_id", 0, NULL, object->telescope_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope_id");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+dimmRow *dimmObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    psF32           sigmax;
+    psF32           sigmay;
+    psF32           fwhm;
+    psF64           ra;
+    psF64           decl;
+    psF32           expttime;
+    char            *telescope_id;
+
+    sigmax = psMetadataLookupF32(&status, md, "sigmax");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sigmax");
+        return false;
+    }
+    sigmay = psMetadataLookupF32(&status, md, "sigmay");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sigmay");
+        return false;
+    }
+    fwhm = psMetadataLookupF32(&status, md, "fwhm");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fwhm");
+        return false;
+    }
+    ra = psMetadataLookupF64(&status, md, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        return false;
+    }
+    decl = psMetadataLookupF64(&status, md, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        return false;
+    }
+    expttime = psMetadataLookupF32(&status, md, "expttime");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item expttime");
+        return false;
+    }
+    telescope_id = psMetadataLookupPtr(&status, md, "telescope_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope_id");
+        return false;
+    }
+
+    return dimmRowAlloc(sigmax, sigmay, fwhm, ra, decl, expttime, telescope_id);
+}
+psArray *dimmSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DIMM_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, DIMM_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DIMM_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++) {
+        dimmRow *object = dimmObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long dimmDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        dimmRow *object = objects->data[i];
+        psMetadata *where = dimmMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DIMM_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from dimm");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void skyp_irRowFree(skyp_irRow *object);
+
+skyp_irRow *skyp_irRowAlloc(psF64 sky_bright, psF64 sky_var, psF64 ra, psF64 decl, psF32 fov_x, psF32 fov_y)
+{
+    skyp_irRow      *object;
+
+    object = psAlloc(sizeof(skyp_irRow));
+    psMemSetDeallocator(object, (psFreeFunc)skyp_irRowFree);
+
+    object->sky_bright = sky_bright;
+    object->sky_var = sky_var;
+    object->ra = ra;
+    object->decl = decl;
+    object->fov_x = fov_x;
+    object->fov_y = fov_y;
+
+    return object;
+}
+
+static void skyp_irRowFree(skyp_irRow *object)
+{
+}
+
+bool skyp_irCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, SKYP_IR_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", SKYP_IR_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_bright", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sky_bright");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_var", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sky_var");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "fov_x", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fov_x");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "fov_y", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fov_y");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, SKYP_IR_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool skyp_irDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, SKYP_IR_TABLE_NAME);
+}
+
+bool skyp_irInsert(psDB * dbh, psF64 sky_bright, psF64 sky_var, psF64 ra, psF64 decl, psF32 fov_x, psF32 fov_y)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_bright", 0, NULL, sky_bright)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sky_bright");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_var", 0, NULL, sky_var)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sky_var");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "fov_x", 0, NULL, fov_x)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fov_x");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "fov_y", 0, NULL, fov_y)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fov_y");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, SKYP_IR_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long skyp_irDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, SKYP_IR_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from skyp_ir");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool skyp_irPop(psDB *dbh, psF64 *sky_bright, psF64 *sky_var, psF64 *ra, psF64 *decl, psF32 *fov_x, psF32 *fov_y)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, SKYP_IR_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", SKYP_IR_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, SKYP_IR_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", SKYP_IR_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, SKYP_IR_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, SKYP_IR_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);
+
+    *sky_bright = psMetadataLookupF64(&status, row, "sky_bright");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sky_bright");
+        psFree(row);
+        return false;
+    }
+    *sky_var = psMetadataLookupF64(&status, row, "sky_var");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sky_var");
+        psFree(row);
+        return false;
+    }
+    *ra = psMetadataLookupF64(&status, row, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        psFree(row);
+        return false;
+    }
+    *decl = psMetadataLookupF64(&status, row, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        psFree(row);
+        return false;
+    }
+    *fov_x = psMetadataLookupF32(&status, row, "fov_x");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fov_x");
+        psFree(row);
+        return false;
+    }
+    *fov_y = psMetadataLookupF32(&status, row, "fov_y");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fov_y");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool skyp_irInsertObject(psDB *dbh, skyp_irRow *object)
+{
+    return skyp_irInsert(dbh, object->sky_bright, object->sky_var, object->ra, object->decl, object->fov_x, object->fov_y);
+}
+
+skyp_irRow *skyp_irPopObject(psDB *dbh)
+{
+    psF64           sky_bright;
+    psF64           sky_var;
+    psF64           ra;
+    psF64           decl;
+    psF32           fov_x;
+    psF32           fov_y;
+
+    if (!skyp_irPop(dbh, &sky_bright, &sky_var, &ra, &decl, &fov_x, &fov_y)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return skyp_irRowAlloc(sky_bright, sky_var, ra, decl, fov_x, fov_y);
+}
+
+bool skyp_irInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  SKYP_IR_TABLE_NAME
+    if (!psFitsMoveExtName(fits, SKYP_IR_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", SKYP_IR_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, SKYP_IR_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool skyp_irPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!skyp_irSelectRowsFits(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",
+                SKYP_IR_TABLE_NAME, SKYP_IR_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 skyp_irSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, SKYP_IR_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, SKYP_IR_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", SKYP_IR_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, SKYP_IR_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *skyp_irMetadataFromObject(const skyp_irRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_bright", 0, NULL, object->sky_bright)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sky_bright");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_var", 0, NULL, object->sky_var)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sky_var");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, object->ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, object->decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "fov_x", 0, NULL, object->fov_x)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fov_x");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "fov_y", 0, NULL, object->fov_y)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fov_y");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+skyp_irRow *skyp_irObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    psF64           sky_bright;
+    psF64           sky_var;
+    psF64           ra;
+    psF64           decl;
+    psF32           fov_x;
+    psF32           fov_y;
+
+    sky_bright = psMetadataLookupF64(&status, md, "sky_bright");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sky_bright");
+        return false;
+    }
+    sky_var = psMetadataLookupF64(&status, md, "sky_var");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sky_var");
+        return false;
+    }
+    ra = psMetadataLookupF64(&status, md, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        return false;
+    }
+    decl = psMetadataLookupF64(&status, md, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        return false;
+    }
+    fov_x = psMetadataLookupF32(&status, md, "fov_x");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fov_x");
+        return false;
+    }
+    fov_y = psMetadataLookupF32(&status, md, "fov_y");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fov_y");
+        return false;
+    }
+
+    return skyp_irRowAlloc(sky_bright, sky_var, ra, decl, fov_x, fov_y);
+}
+psArray *skyp_irSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, SKYP_IR_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, SKYP_IR_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", SKYP_IR_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++) {
+        skyp_irRow *object = skyp_irObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long skyp_irDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        skyp_irRow *object = objects->data[i];
+        psMetadata *where = skyp_irMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, SKYP_IR_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from skyp_ir");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void domeRowFree(domeRow *object);
+
+domeRow *domeRowAlloc(psF32 az, bool open, bool light, bool track)
+{
+    domeRow         *object;
+
+    object = psAlloc(sizeof(domeRow));
+    psMemSetDeallocator(object, (psFreeFunc)domeRowFree);
+
+    object->az = az;
+    object->open = open;
+    object->light = light;
+    object->track = track;
+
+    return object;
+}
+
+static void domeRowFree(domeRow *object)
+{
+}
+
+bool domeCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, DOME_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", DOME_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "az", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item az");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "open", PS_DATA_BOOL, NULL, true)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item open");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "light", PS_DATA_BOOL, NULL, true)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item light");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "track", PS_DATA_BOOL, NULL, true)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item track");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, DOME_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool domeDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DOME_TABLE_NAME);
+}
+
+bool domeInsert(psDB * dbh, psF32 az, bool open, bool light, bool track)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "az", 0, NULL, az)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item az");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "open", PS_DATA_BOOL, NULL, open)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item open");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "light", PS_DATA_BOOL, NULL, light)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item light");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "track", PS_DATA_BOOL, NULL, track)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item track");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, DOME_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long domeDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, DOME_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from dome");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool domePop(psDB *dbh, psF32 *az, bool *open, bool *light, bool *track)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, DOME_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DOME_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, DOME_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DOME_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, DOME_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, DOME_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);
+
+    *az = psMetadataLookupF32(&status, row, "az");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item az");
+        psFree(row);
+        return false;
+    }
+    *open = psMetadataLookupBool(&status, row, "open");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item open");
+        psFree(row);
+        return false;
+    }
+    *light = psMetadataLookupBool(&status, row, "light");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item light");
+        psFree(row);
+        return false;
+    }
+    *track = psMetadataLookupBool(&status, row, "track");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item track");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool domeInsertObject(psDB *dbh, domeRow *object)
+{
+    return domeInsert(dbh, object->az, object->open, object->light, object->track);
+}
+
+domeRow *domePopObject(psDB *dbh)
+{
+    psF32           az;
+    bool            open;
+    bool            light;
+    bool            track;
+
+    if (!domePop(dbh, &az, &open, &light, &track)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return domeRowAlloc(az, open, light, track);
+}
+
+bool domeInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DOME_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DOME_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DOME_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, DOME_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool domePopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!domeSelectRowsFits(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",
+                DOME_TABLE_NAME, DOME_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 domeSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DOME_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, DOME_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DOME_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DOME_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *domeMetadataFromObject(const domeRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "az", 0, NULL, object->az)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item az");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "open", PS_DATA_BOOL, NULL, object->open)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item open");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "light", PS_DATA_BOOL, NULL, object->light)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item light");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "track", PS_DATA_BOOL, NULL, object->track)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item track");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+domeRow *domeObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    psF32           az;
+    bool            open;
+    bool            light;
+    bool            track;
+
+    az = psMetadataLookupF32(&status, md, "az");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item az");
+        return false;
+    }
+    open = psMetadataLookupBool(&status, md, "open");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item open");
+        return false;
+    }
+    light = psMetadataLookupBool(&status, md, "light");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item light");
+        return false;
+    }
+    track = psMetadataLookupBool(&status, md, "track");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item track");
+        return false;
+    }
+
+    return domeRowAlloc(az, open, light, track);
+}
+psArray *domeSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DOME_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, DOME_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DOME_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++) {
+        domeRow *object = domeObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long domeDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        domeRow *object = objects->data[i];
+        psMetadata *where = domeMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DOME_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from dome");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void telescopeRowFree(telescopeRow *object);
+
+telescopeRow *telescopeRowAlloc(const char *guide, psF32 alt, psF32 az, psF64 ra, psF64 decl)
+{
+    telescopeRow    *object;
+
+    object = psAlloc(sizeof(telescopeRow));
+    psMemSetDeallocator(object, (psFreeFunc)telescopeRowFree);
+
+    object->guide = psStringCopy(guide);
+    object->alt = alt;
+    object->az = az;
+    object->ra = ra;
+    object->decl = decl;
+
+    return object;
+}
+
+static void telescopeRowFree(telescopeRow *object)
+{
+    psFree(object->guide);
+}
+
+bool telescopeCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, TELESCOPE_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", TELESCOPE_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "guide", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item guide");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "alt", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item alt");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "az", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item az");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, TELESCOPE_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool telescopeDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, TELESCOPE_TABLE_NAME);
+}
+
+bool telescopeInsert(psDB * dbh, const char *guide, psF32 alt, psF32 az, psF64 ra, psF64 decl)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "guide", 0, NULL, guide)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item guide");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "alt", 0, NULL, alt)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item alt");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "az", 0, NULL, az)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item az");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, TELESCOPE_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long telescopeDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, TELESCOPE_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from telescope");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool telescopePop(psDB *dbh, char **guide, psF32 *alt, psF32 *az, psF64 *ra, psF64 *decl)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, TELESCOPE_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", TELESCOPE_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, TELESCOPE_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", TELESCOPE_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, TELESCOPE_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, TELESCOPE_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);
+
+    *guide = psMetadataLookupPtr(&status, row, "guide");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item guide");
+        psFree(row);
+        return false;
+    }
+    *alt = psMetadataLookupF32(&status, row, "alt");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item alt");
+        psFree(row);
+        return false;
+    }
+    *az = psMetadataLookupF32(&status, row, "az");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item az");
+        psFree(row);
+        return false;
+    }
+    *ra = psMetadataLookupF64(&status, row, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        psFree(row);
+        return false;
+    }
+    *decl = psMetadataLookupF64(&status, row, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool telescopeInsertObject(psDB *dbh, telescopeRow *object)
+{
+    return telescopeInsert(dbh, object->guide, object->alt, object->az, object->ra, object->decl);
+}
+
+telescopeRow *telescopePopObject(psDB *dbh)
+{
+    char            guide[256];
+    psF32           alt;
+    psF32           az;
+    psF64           ra;
+    psF64           decl;
+
+    if (!telescopePop(dbh, (char **)&guide, &alt, &az, &ra, &decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return telescopeRowAlloc(guide, alt, az, ra, decl);
+}
+
+bool telescopeInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  TELESCOPE_TABLE_NAME
+    if (!psFitsMoveExtName(fits, TELESCOPE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", TELESCOPE_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, TELESCOPE_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool telescopePopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!telescopeSelectRowsFits(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",
+                TELESCOPE_TABLE_NAME, TELESCOPE_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 telescopeSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, TELESCOPE_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, TELESCOPE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", TELESCOPE_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, TELESCOPE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *telescopeMetadataFromObject(const telescopeRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "guide", 0, NULL, object->guide)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item guide");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "alt", 0, NULL, object->alt)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item alt");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "az", 0, NULL, object->az)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item az");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, object->ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, object->decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+telescopeRow *telescopeObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *guide;
+    psF32           alt;
+    psF32           az;
+    psF64           ra;
+    psF64           decl;
+
+    guide = psMetadataLookupPtr(&status, md, "guide");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item guide");
+        return false;
+    }
+    alt = psMetadataLookupF32(&status, md, "alt");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item alt");
+        return false;
+    }
+    az = psMetadataLookupF32(&status, md, "az");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item az");
+        return false;
+    }
+    ra = psMetadataLookupF64(&status, md, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        return false;
+    }
+    decl = psMetadataLookupF64(&status, md, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        return false;
+    }
+
+    return telescopeRowAlloc(guide, alt, az, ra, decl);
+}
+psArray *telescopeSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, TELESCOPE_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, TELESCOPE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", TELESCOPE_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++) {
+        telescopeRow *object = telescopeObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long telescopeDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        telescopeRow *object = objects->data[i];
+        psMetadata *where = telescopeMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, TELESCOPE_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from telescope");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void summitExpRowFree(summitExpRow *object);
+
+summitExpRow *summitExpRowAlloc(const char *exp_id, const char *camera, const char *telescope, const char *exp_type, const char *uri)
+{
+    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->exp_type = psStringCopy(exp_type);
+    object->uri = psStringCopy(uri);
+
+    return object;
+}
+
+static void summitExpRowFree(summitExpRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->camera);
+    psFree(object->telescope);
+    psFree(object->exp_type);
+    psFree(object->uri);
+}
+
+bool summitExpCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, SUMMITEXP_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", SUMMITEXP_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+
+    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, const char *exp_type, const char *uri)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, exp_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+
+    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 summitExpPop(psDB *dbh, char **exp_id, char **camera, char **telescope, char **exp_type, char **uri)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, SUMMITEXP_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", SUMMITEXP_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, SUMMITEXP_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", SUMMITEXP_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, SUMMITEXP_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, SUMMITEXP_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);
+
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *camera = psMetadataLookupPtr(&status, row, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        psFree(row);
+        return false;
+    }
+    *telescope = psMetadataLookupPtr(&status, row, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        psFree(row);
+        return false;
+    }
+    *exp_type = psMetadataLookupPtr(&status, row, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        psFree(row);
+        return false;
+    }
+    *uri = psMetadataLookupPtr(&status, row, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool summitExpInsertObject(psDB *dbh, summitExpRow *object)
+{
+    return summitExpInsert(dbh, object->exp_id, object->camera, object->telescope, object->exp_type, object->uri);
+}
+
+summitExpRow *summitExpPopObject(psDB *dbh)
+{
+    char            exp_id[256];
+    char            camera[256];
+    char            telescope[256];
+    char            exp_type[256];
+    char            uri[256];
+
+    if (!summitExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, (char **)&uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return summitExpRowAlloc(exp_id, camera, telescope, exp_type, uri);
+}
+
+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 summitExpPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!summitExpSelectRowsFits(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",
+                SUMMITEXP_TABLE_NAME, SUMMITEXP_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 summitExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, SUMMITEXP_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, SUMMITEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", SUMMITEXP_INDEX_NAME);
+            psFree(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;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, object->exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, object->camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, object->telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, object->exp_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, object->uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+summitExpRow *summitExpObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    char            *uri;
+
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    camera = psMetadataLookupPtr(&status, md, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        return false;
+    }
+    telescope = psMetadataLookupPtr(&status, md, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        return false;
+    }
+    exp_type = psMetadataLookupPtr(&status, md, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        return false;
+    }
+    uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+
+    return summitExpRowAlloc(exp_id, camera, telescope, exp_type, uri);
+}
+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;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, SUMMITEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", SUMMITEXP_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++) {
+        summitExpRow *object = summitExpObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+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;
+}
+static void pzPendingExpRowFree(pzPendingExpRow *object);
+
+pzPendingExpRow *pzPendingExpRowAlloc(const char *exp_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles)
+{
+    pzPendingExpRow *object;
+
+    object = psAlloc(sizeof(pzPendingExpRow));
+    psMemSetDeallocator(object, (psFreeFunc)pzPendingExpRowFree);
+
+    object->exp_id = psStringCopy(exp_id);
+    object->camera = psStringCopy(camera);
+    object->telescope = psStringCopy(telescope);
+    object->exp_type = psStringCopy(exp_type);
+    object->imfiles = imfiles;
+
+    return object;
+}
+
+static void pzPendingExpRowFree(pzPendingExpRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->camera);
+    psFree(object->telescope);
+    psFree(object->exp_type);
+}
+
+bool pzPendingExpCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, PZPENDINGEXP_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", PZPENDINGEXP_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+
+    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, const char *exp_type, psS32 imfiles)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, exp_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+
+    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 pzPendingExpPop(psDB *dbh, char **exp_id, char **camera, char **telescope, char **exp_type, psS32 *imfiles)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, PZPENDINGEXP_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", PZPENDINGEXP_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, PZPENDINGEXP_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", PZPENDINGEXP_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, PZPENDINGEXP_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, PZPENDINGEXP_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);
+
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *camera = psMetadataLookupPtr(&status, row, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        psFree(row);
+        return false;
+    }
+    *telescope = psMetadataLookupPtr(&status, row, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        psFree(row);
+        return false;
+    }
+    *exp_type = psMetadataLookupPtr(&status, row, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        psFree(row);
+        return false;
+    }
+    *imfiles = psMetadataLookupS32(&status, row, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool pzPendingExpInsertObject(psDB *dbh, pzPendingExpRow *object)
+{
+    return pzPendingExpInsert(dbh, object->exp_id, object->camera, object->telescope, object->exp_type, object->imfiles);
+}
+
+pzPendingExpRow *pzPendingExpPopObject(psDB *dbh)
+{
+    char            exp_id[256];
+    char            camera[256];
+    char            telescope[256];
+    char            exp_type[256];
+    psS32           imfiles;
+
+    if (!pzPendingExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return pzPendingExpRowAlloc(exp_id, camera, telescope, exp_type, imfiles);
+}
+
+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 pzPendingExpPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!pzPendingExpSelectRowsFits(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",
+                PZPENDINGEXP_TABLE_NAME, PZPENDINGEXP_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 pzPendingExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, PZPENDINGEXP_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, PZPENDINGEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", PZPENDINGEXP_INDEX_NAME);
+            psFree(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;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, object->exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, object->camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, object->telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, object->exp_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, object->imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+pzPendingExpRow *pzPendingExpObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    camera = psMetadataLookupPtr(&status, md, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        return false;
+    }
+    telescope = psMetadataLookupPtr(&status, md, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        return false;
+    }
+    exp_type = psMetadataLookupPtr(&status, md, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        return false;
+    }
+    imfiles = psMetadataLookupS32(&status, md, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        return false;
+    }
+
+    return pzPendingExpRowAlloc(exp_id, camera, telescope, exp_type, imfiles);
+}
+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;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, PZPENDINGEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", PZPENDINGEXP_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++) {
+        pzPendingExpRow *object = pzPendingExpObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+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;
+}
+static void pzPendingImfileRowFree(pzPendingImfileRow *object);
+
+pzPendingImfileRow *pzPendingImfileRowAlloc(const char *exp_id, psS32 bytes, const char *md5sum, const char *class, const char *class_id, const char *uri)
+{
+    pzPendingImfileRow *object;
+
+    object = psAlloc(sizeof(pzPendingImfileRow));
+    psMemSetDeallocator(object, (psFreeFunc)pzPendingImfileRowFree);
+
+    object->exp_id = psStringCopy(exp_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 pzPendingImfileRowFree(pzPendingImfileRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->md5sum);
+    psFree(object->class);
+    psFree(object->class_id);
+    psFree(object->uri);
+}
+
+bool pzPendingImfileCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, PZPENDINGIMFILE_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", PZPENDINGIMFILE_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "bytes", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bytes");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "md5sum", 0, NULL, "32")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item md5sum");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+
+    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, psS32 bytes, const char *md5sum, const char *class, const char *class_id, const char *uri)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "bytes", 0, NULL, bytes)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bytes");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "md5sum", 0, NULL, md5sum)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item md5sum");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class", 0, NULL, class)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
+        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 (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+
+    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 pzPendingImfilePop(psDB *dbh, char **exp_id, psS32 *bytes, char **md5sum, char **class, char **class_id, char **uri)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, PZPENDINGIMFILE_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", PZPENDINGIMFILE_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, PZPENDINGIMFILE_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", PZPENDINGIMFILE_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, PZPENDINGIMFILE_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, PZPENDINGIMFILE_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);
+
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *bytes = psMetadataLookupS32(&status, row, "bytes");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bytes");
+        psFree(row);
+        return false;
+    }
+    *md5sum = psMetadataLookupPtr(&status, row, "md5sum");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item md5sum");
+        psFree(row);
+        return false;
+    }
+    *class = psMetadataLookupPtr(&status, row, "class");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class");
+        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;
+    }
+    *uri = psMetadataLookupPtr(&status, row, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool pzPendingImfileInsertObject(psDB *dbh, pzPendingImfileRow *object)
+{
+    return pzPendingImfileInsert(dbh, object->exp_id, object->bytes, object->md5sum, object->class, object->class_id, object->uri);
+}
+
+pzPendingImfileRow *pzPendingImfilePopObject(psDB *dbh)
+{
+    char            exp_id[256];
+    psS32           bytes;
+    char            md5sum[256];
+    char            class[256];
+    char            class_id[256];
+    char            uri[256];
+
+    if (!pzPendingImfilePop(dbh, (char **)&exp_id, &bytes, (char **)&md5sum, (char **)&class, (char **)&class_id, (char **)&uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return pzPendingImfileRowAlloc(exp_id, bytes, md5sum, class, class_id, uri);
+}
+
+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 pzPendingImfilePopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!pzPendingImfileSelectRowsFits(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",
+                PZPENDINGIMFILE_TABLE_NAME, PZPENDINGIMFILE_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 pzPendingImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, PZPENDINGIMFILE_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, PZPENDINGIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", PZPENDINGIMFILE_INDEX_NAME);
+            psFree(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;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, object->exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "bytes", 0, NULL, object->bytes)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bytes");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "md5sum", 0, NULL, object->md5sum)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item md5sum");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class", 0, NULL, object->class)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
+        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 (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, object->uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+pzPendingImfileRow *pzPendingImfileObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *exp_id;
+    psS32           bytes;
+    char            *md5sum;
+    char            *class;
+    char            *class_id;
+    char            *uri;
+
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    bytes = psMetadataLookupS32(&status, md, "bytes");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bytes");
+        return false;
+    }
+    md5sum = psMetadataLookupPtr(&status, md, "md5sum");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item md5sum");
+        return false;
+    }
+    class = psMetadataLookupPtr(&status, md, "class");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class");
+        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;
+    }
+    uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+
+    return pzPendingImfileRowAlloc(exp_id, bytes, md5sum, class, class_id, uri);
+}
+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;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, PZPENDINGIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", PZPENDINGIMFILE_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++) {
+        pzPendingImfileRow *object = pzPendingImfileObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+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;
+}
+static void newExpRowFree(newExpRow *object);
+
+newExpRow *newExpRowAlloc(const char *exp_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles)
+{
+    newExpRow       *object;
+
+    object = psAlloc(sizeof(newExpRow));
+    psMemSetDeallocator(object, (psFreeFunc)newExpRowFree);
+
+    object->exp_id = psStringCopy(exp_id);
+    object->camera = psStringCopy(camera);
+    object->telescope = psStringCopy(telescope);
+    object->exp_type = psStringCopy(exp_type);
+    object->imfiles = imfiles;
+
+    return object;
+}
+
+static void newExpRowFree(newExpRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->camera);
+    psFree(object->telescope);
+    psFree(object->exp_type);
+}
+
+bool newExpCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, NEWEXP_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", NEWEXP_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+
+    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_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, exp_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+
+    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 newExpPop(psDB *dbh, char **exp_id, char **camera, char **telescope, char **exp_type, psS32 *imfiles)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, NEWEXP_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", NEWEXP_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, NEWEXP_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", NEWEXP_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, NEWEXP_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, NEWEXP_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);
+
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *camera = psMetadataLookupPtr(&status, row, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        psFree(row);
+        return false;
+    }
+    *telescope = psMetadataLookupPtr(&status, row, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        psFree(row);
+        return false;
+    }
+    *exp_type = psMetadataLookupPtr(&status, row, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        psFree(row);
+        return false;
+    }
+    *imfiles = psMetadataLookupS32(&status, row, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool newExpInsertObject(psDB *dbh, newExpRow *object)
+{
+    return newExpInsert(dbh, object->exp_id, object->camera, object->telescope, object->exp_type, object->imfiles);
+}
+
+newExpRow *newExpPopObject(psDB *dbh)
+{
+    char            exp_id[256];
+    char            camera[256];
+    char            telescope[256];
+    char            exp_type[256];
+    psS32           imfiles;
+
+    if (!newExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return newExpRowAlloc(exp_id, camera, telescope, exp_type, imfiles);
+}
+
+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 newExpPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!newExpSelectRowsFits(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",
+                NEWEXP_TABLE_NAME, NEWEXP_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 newExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, NEWEXP_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, NEWEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", NEWEXP_INDEX_NAME);
+            psFree(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;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, object->exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, object->camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, object->telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, object->exp_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, object->imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+newExpRow *newExpObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    camera = psMetadataLookupPtr(&status, md, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        return false;
+    }
+    telescope = psMetadataLookupPtr(&status, md, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        return false;
+    }
+    exp_type = psMetadataLookupPtr(&status, md, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        return false;
+    }
+    imfiles = psMetadataLookupS32(&status, md, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        return false;
+    }
+
+    return newExpRowAlloc(exp_id, camera, telescope, exp_type, imfiles);
+}
+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;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, NEWEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", NEWEXP_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++) {
+        newExpRow *object = newExpObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+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;
+}
+static void newImfileRowFree(newImfileRow *object);
+
+newImfileRow *newImfileRowAlloc(const char *exp_id, const char *class, const char *class_id, const char *uri)
+{
+    newImfileRow    *object;
+
+    object = psAlloc(sizeof(newImfileRow));
+    psMemSetDeallocator(object, (psFreeFunc)newImfileRowFree);
+
+    object->exp_id = psStringCopy(exp_id);
+    object->class = psStringCopy(class);
+    object->class_id = psStringCopy(class_id);
+    object->uri = psStringCopy(uri);
+
+    return object;
+}
+
+static void newImfileRowFree(newImfileRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->class);
+    psFree(object->class_id);
+    psFree(object->uri);
+}
+
+bool newImfileCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, NEWIMFILE_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", NEWIMFILE_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
+        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 (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+
+    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_id, const char *class, const char *class_id, const char *uri)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class", 0, NULL, class)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
+        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 (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+
+    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 newImfilePop(psDB *dbh, char **exp_id, char **class, char **class_id, char **uri)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, NEWIMFILE_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", NEWIMFILE_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, NEWIMFILE_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", NEWIMFILE_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, NEWIMFILE_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, NEWIMFILE_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);
+
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *class = psMetadataLookupPtr(&status, row, "class");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class");
+        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;
+    }
+    *uri = psMetadataLookupPtr(&status, row, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool newImfileInsertObject(psDB *dbh, newImfileRow *object)
+{
+    return newImfileInsert(dbh, object->exp_id, object->class, object->class_id, object->uri);
+}
+
+newImfileRow *newImfilePopObject(psDB *dbh)
+{
+    char            exp_id[256];
+    char            class[256];
+    char            class_id[256];
+    char            uri[256];
+
+    if (!newImfilePop(dbh, (char **)&exp_id, (char **)&class, (char **)&class_id, (char **)&uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return newImfileRowAlloc(exp_id, class, class_id, uri);
+}
+
+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 newImfilePopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!newImfileSelectRowsFits(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",
+                NEWIMFILE_TABLE_NAME, NEWIMFILE_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 newImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, NEWIMFILE_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, NEWIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", NEWIMFILE_INDEX_NAME);
+            psFree(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;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, object->exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class", 0, NULL, object->class)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
+        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 (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, object->uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+newImfileRow *newImfileObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *exp_id;
+    char            *class;
+    char            *class_id;
+    char            *uri;
+
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    class = psMetadataLookupPtr(&status, md, "class");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class");
+        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;
+    }
+    uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+
+    return newImfileRowAlloc(exp_id, 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;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, NEWIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", NEWIMFILE_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++) {
+        newImfileRow *object = newImfileObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+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;
+}
+static void rawDetrendExpRowFree(rawDetrendExpRow *object);
+
+rawDetrendExpRow *rawDetrendExpRowAlloc(const char *exp_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles, const char *filter, psF32 airmass, psF64 ra, psF64 decl, psF32 exp_time, psF64 background)
+{
+    rawDetrendExpRow *object;
+
+    object = psAlloc(sizeof(rawDetrendExpRow));
+    psMemSetDeallocator(object, (psFreeFunc)rawDetrendExpRowFree);
+
+    object->exp_id = psStringCopy(exp_id);
+    object->camera = psStringCopy(camera);
+    object->telescope = psStringCopy(telescope);
+    object->exp_type = psStringCopy(exp_type);
+    object->imfiles = imfiles;
+    object->filter = psStringCopy(filter);
+    object->airmass = airmass;
+    object->ra = ra;
+    object->decl = decl;
+    object->exp_time = exp_time;
+    object->background = background;
+
+    return object;
+}
+
+static void rawDetrendExpRowFree(rawDetrendExpRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->camera);
+    psFree(object->telescope);
+    psFree(object->exp_type);
+    psFree(object->filter);
+}
+
+bool rawDetrendExpCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, RAWDETRENDEXP_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", RAWDETRENDEXP_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exp_time", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "background", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item background");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, RAWDETRENDEXP_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool rawDetrendExpDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, RAWDETRENDEXP_TABLE_NAME);
+}
+
+bool rawDetrendExpInsert(psDB * dbh, const char *exp_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles, const char *filter, psF32 airmass, psF64 ra, psF64 decl, psF32 exp_time, psF64 background)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, exp_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, filter)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass", 0, NULL, airmass)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exp_time", 0, NULL, exp_time)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "background", 0, NULL, background)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item background");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, RAWDETRENDEXP_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long rawDetrendExpDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, RAWDETRENDEXP_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from rawDetrendExp");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool rawDetrendExpPop(psDB *dbh, char **exp_id, char **camera, char **telescope, char **exp_type, psS32 *imfiles, char **filter, psF32 *airmass, psF64 *ra, psF64 *decl, psF32 *exp_time, psF64 *background)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, RAWDETRENDEXP_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", RAWDETRENDEXP_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, RAWDETRENDEXP_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", RAWDETRENDEXP_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, RAWDETRENDEXP_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, RAWDETRENDEXP_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);
+
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *camera = psMetadataLookupPtr(&status, row, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        psFree(row);
+        return false;
+    }
+    *telescope = psMetadataLookupPtr(&status, row, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        psFree(row);
+        return false;
+    }
+    *exp_type = psMetadataLookupPtr(&status, row, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        psFree(row);
+        return false;
+    }
+    *imfiles = psMetadataLookupS32(&status, row, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        psFree(row);
+        return false;
+    }
+    *filter = psMetadataLookupPtr(&status, row, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        psFree(row);
+        return false;
+    }
+    *airmass = psMetadataLookupF32(&status, row, "airmass");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item airmass");
+        psFree(row);
+        return false;
+    }
+    *ra = psMetadataLookupF64(&status, row, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        psFree(row);
+        return false;
+    }
+    *decl = psMetadataLookupF64(&status, row, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        psFree(row);
+        return false;
+    }
+    *exp_time = psMetadataLookupF32(&status, row, "exp_time");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_time");
+        psFree(row);
+        return false;
+    }
+    *background = psMetadataLookupF64(&status, row, "background");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item background");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool rawDetrendExpInsertObject(psDB *dbh, rawDetrendExpRow *object)
+{
+    return rawDetrendExpInsert(dbh, object->exp_id, object->camera, object->telescope, object->exp_type, object->imfiles, object->filter, object->airmass, object->ra, object->decl, object->exp_time, object->background);
+}
+
+rawDetrendExpRow *rawDetrendExpPopObject(psDB *dbh)
+{
+    char            exp_id[256];
+    char            camera[256];
+    char            telescope[256];
+    char            exp_type[256];
+    psS32           imfiles;
+    char            filter[256];
+    psF32           airmass;
+    psF64           ra;
+    psF64           decl;
+    psF32           exp_time;
+    psF64           background;
+
+    if (!rawDetrendExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles, (char **)&filter, &airmass, &ra, &decl, &exp_time, &background)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return rawDetrendExpRowAlloc(exp_id, camera, telescope, exp_type, imfiles, filter, airmass, ra, decl, exp_time, background);
+}
+
+bool rawDetrendExpInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  RAWDETRENDEXP_TABLE_NAME
+    if (!psFitsMoveExtName(fits, RAWDETRENDEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", RAWDETRENDEXP_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, RAWDETRENDEXP_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool rawDetrendExpPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!rawDetrendExpSelectRowsFits(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",
+                RAWDETRENDEXP_TABLE_NAME, RAWDETRENDEXP_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 rawDetrendExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, RAWDETRENDEXP_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, RAWDETRENDEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", RAWDETRENDEXP_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, RAWDETRENDEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *rawDetrendExpMetadataFromObject(const rawDetrendExpRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, object->exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, object->camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, object->telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, object->exp_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, object->imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, object->filter)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass", 0, NULL, object->airmass)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, object->ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, object->decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exp_time", 0, NULL, object->exp_time)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "background", 0, NULL, object->background)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item background");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+rawDetrendExpRow *rawDetrendExpObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+    char            *filter;
+    psF32           airmass;
+    psF64           ra;
+    psF64           decl;
+    psF32           exp_time;
+    psF64           background;
+
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    camera = psMetadataLookupPtr(&status, md, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        return false;
+    }
+    telescope = psMetadataLookupPtr(&status, md, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        return false;
+    }
+    exp_type = psMetadataLookupPtr(&status, md, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        return false;
+    }
+    imfiles = psMetadataLookupS32(&status, md, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        return false;
+    }
+    filter = psMetadataLookupPtr(&status, md, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        return false;
+    }
+    airmass = psMetadataLookupF32(&status, md, "airmass");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item airmass");
+        return false;
+    }
+    ra = psMetadataLookupF64(&status, md, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        return false;
+    }
+    decl = psMetadataLookupF64(&status, md, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        return false;
+    }
+    exp_time = psMetadataLookupF32(&status, md, "exp_time");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_time");
+        return false;
+    }
+    background = psMetadataLookupF64(&status, md, "background");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item background");
+        return false;
+    }
+
+    return rawDetrendExpRowAlloc(exp_id, camera, telescope, exp_type, imfiles, filter, airmass, ra, decl, exp_time, background);
+}
+psArray *rawDetrendExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, RAWDETRENDEXP_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, RAWDETRENDEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", RAWDETRENDEXP_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++) {
+        rawDetrendExpRow *object = rawDetrendExpObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long rawDetrendExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        rawDetrendExpRow *object = objects->data[i];
+        psMetadata *where = rawDetrendExpMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, RAWDETRENDEXP_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from rawDetrendExp");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void rawScienceExpRowFree(rawScienceExpRow *object);
+
+rawScienceExpRow *rawScienceExpRowAlloc(const char *exp_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles, const char *filter, psF32 airmass, psF64 ra, psF64 decl, psF32 exp_time, psF64 background)
+{
+    rawScienceExpRow *object;
+
+    object = psAlloc(sizeof(rawScienceExpRow));
+    psMemSetDeallocator(object, (psFreeFunc)rawScienceExpRowFree);
+
+    object->exp_id = psStringCopy(exp_id);
+    object->camera = psStringCopy(camera);
+    object->telescope = psStringCopy(telescope);
+    object->exp_type = psStringCopy(exp_type);
+    object->imfiles = imfiles;
+    object->filter = psStringCopy(filter);
+    object->airmass = airmass;
+    object->ra = ra;
+    object->decl = decl;
+    object->exp_time = exp_time;
+    object->background = background;
+
+    return object;
+}
+
+static void rawScienceExpRowFree(rawScienceExpRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->camera);
+    psFree(object->telescope);
+    psFree(object->exp_type);
+    psFree(object->filter);
+}
+
+bool rawScienceExpCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, RAWSCIENCEEXP_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", RAWSCIENCEEXP_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exp_time", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "background", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item background");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, RAWSCIENCEEXP_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool rawScienceExpDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, RAWSCIENCEEXP_TABLE_NAME);
+}
+
+bool rawScienceExpInsert(psDB * dbh, const char *exp_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles, const char *filter, psF32 airmass, psF64 ra, psF64 decl, psF32 exp_time, psF64 background)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, exp_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, filter)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass", 0, NULL, airmass)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exp_time", 0, NULL, exp_time)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "background", 0, NULL, background)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item background");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, RAWSCIENCEEXP_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long rawScienceExpDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, RAWSCIENCEEXP_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from rawScienceExp");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool rawScienceExpPop(psDB *dbh, char **exp_id, char **camera, char **telescope, char **exp_type, psS32 *imfiles, char **filter, psF32 *airmass, psF64 *ra, psF64 *decl, psF32 *exp_time, psF64 *background)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, RAWSCIENCEEXP_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", RAWSCIENCEEXP_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, RAWSCIENCEEXP_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", RAWSCIENCEEXP_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, RAWSCIENCEEXP_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, RAWSCIENCEEXP_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);
+
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *camera = psMetadataLookupPtr(&status, row, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        psFree(row);
+        return false;
+    }
+    *telescope = psMetadataLookupPtr(&status, row, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        psFree(row);
+        return false;
+    }
+    *exp_type = psMetadataLookupPtr(&status, row, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        psFree(row);
+        return false;
+    }
+    *imfiles = psMetadataLookupS32(&status, row, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        psFree(row);
+        return false;
+    }
+    *filter = psMetadataLookupPtr(&status, row, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        psFree(row);
+        return false;
+    }
+    *airmass = psMetadataLookupF32(&status, row, "airmass");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item airmass");
+        psFree(row);
+        return false;
+    }
+    *ra = psMetadataLookupF64(&status, row, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        psFree(row);
+        return false;
+    }
+    *decl = psMetadataLookupF64(&status, row, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        psFree(row);
+        return false;
+    }
+    *exp_time = psMetadataLookupF32(&status, row, "exp_time");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_time");
+        psFree(row);
+        return false;
+    }
+    *background = psMetadataLookupF64(&status, row, "background");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item background");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool rawScienceExpInsertObject(psDB *dbh, rawScienceExpRow *object)
+{
+    return rawScienceExpInsert(dbh, object->exp_id, object->camera, object->telescope, object->exp_type, object->imfiles, object->filter, object->airmass, object->ra, object->decl, object->exp_time, object->background);
+}
+
+rawScienceExpRow *rawScienceExpPopObject(psDB *dbh)
+{
+    char            exp_id[256];
+    char            camera[256];
+    char            telescope[256];
+    char            exp_type[256];
+    psS32           imfiles;
+    char            filter[256];
+    psF32           airmass;
+    psF64           ra;
+    psF64           decl;
+    psF32           exp_time;
+    psF64           background;
+
+    if (!rawScienceExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles, (char **)&filter, &airmass, &ra, &decl, &exp_time, &background)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return rawScienceExpRowAlloc(exp_id, camera, telescope, exp_type, imfiles, filter, airmass, ra, decl, exp_time, background);
+}
+
+bool rawScienceExpInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  RAWSCIENCEEXP_TABLE_NAME
+    if (!psFitsMoveExtName(fits, RAWSCIENCEEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", RAWSCIENCEEXP_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, RAWSCIENCEEXP_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool rawScienceExpPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!rawScienceExpSelectRowsFits(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",
+                RAWSCIENCEEXP_TABLE_NAME, RAWSCIENCEEXP_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 rawScienceExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, RAWSCIENCEEXP_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, RAWSCIENCEEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", RAWSCIENCEEXP_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, RAWSCIENCEEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *rawScienceExpMetadataFromObject(const rawScienceExpRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, object->exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, object->camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, object->telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, object->exp_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, object->imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, object->filter)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass", 0, NULL, object->airmass)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, object->ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, object->decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exp_time", 0, NULL, object->exp_time)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "background", 0, NULL, object->background)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item background");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+rawScienceExpRow *rawScienceExpObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+    char            *filter;
+    psF32           airmass;
+    psF64           ra;
+    psF64           decl;
+    psF32           exp_time;
+    psF64           background;
+
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    camera = psMetadataLookupPtr(&status, md, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        return false;
+    }
+    telescope = psMetadataLookupPtr(&status, md, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        return false;
+    }
+    exp_type = psMetadataLookupPtr(&status, md, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        return false;
+    }
+    imfiles = psMetadataLookupS32(&status, md, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        return false;
+    }
+    filter = psMetadataLookupPtr(&status, md, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        return false;
+    }
+    airmass = psMetadataLookupF32(&status, md, "airmass");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item airmass");
+        return false;
+    }
+    ra = psMetadataLookupF64(&status, md, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        return false;
+    }
+    decl = psMetadataLookupF64(&status, md, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        return false;
+    }
+    exp_time = psMetadataLookupF32(&status, md, "exp_time");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_time");
+        return false;
+    }
+    background = psMetadataLookupF64(&status, md, "background");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item background");
+        return false;
+    }
+
+    return rawScienceExpRowAlloc(exp_id, camera, telescope, exp_type, imfiles, filter, airmass, ra, decl, exp_time, background);
+}
+psArray *rawScienceExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, RAWSCIENCEEXP_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, RAWSCIENCEEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", RAWSCIENCEEXP_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++) {
+        rawScienceExpRow *object = rawScienceExpObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long rawScienceExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        rawScienceExpRow *object = objects->data[i];
+        psMetadata *where = rawScienceExpMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, RAWSCIENCEEXP_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from rawScienceExp");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void rawImfileRowFree(rawImfileRow *object);
+
+rawImfileRow *rawImfileRowAlloc(const char *exp_id, const char *class, const char *class_id, const char *uri)
+{
+    rawImfileRow    *object;
+
+    object = psAlloc(sizeof(rawImfileRow));
+    psMemSetDeallocator(object, (psFreeFunc)rawImfileRowFree);
+
+    object->exp_id = psStringCopy(exp_id);
+    object->class = psStringCopy(class);
+    object->class_id = psStringCopy(class_id);
+    object->uri = psStringCopy(uri);
+
+    return object;
+}
+
+static void rawImfileRowFree(rawImfileRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->class);
+    psFree(object->class_id);
+    psFree(object->uri);
+}
+
+bool rawImfileCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, RAWIMFILE_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", RAWIMFILE_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
+        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 (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+
+    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_id, const char *class, const char *class_id, const char *uri)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class", 0, NULL, class)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
+        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 (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+
+    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 rawImfilePop(psDB *dbh, char **exp_id, char **class, char **class_id, char **uri)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, RAWIMFILE_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", RAWIMFILE_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, RAWIMFILE_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", RAWIMFILE_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, RAWIMFILE_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, RAWIMFILE_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);
+
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *class = psMetadataLookupPtr(&status, row, "class");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class");
+        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;
+    }
+    *uri = psMetadataLookupPtr(&status, row, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool rawImfileInsertObject(psDB *dbh, rawImfileRow *object)
+{
+    return rawImfileInsert(dbh, object->exp_id, object->class, object->class_id, object->uri);
+}
+
+rawImfileRow *rawImfilePopObject(psDB *dbh)
+{
+    char            exp_id[256];
+    char            class[256];
+    char            class_id[256];
+    char            uri[256];
+
+    if (!rawImfilePop(dbh, (char **)&exp_id, (char **)&class, (char **)&class_id, (char **)&uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return rawImfileRowAlloc(exp_id, class, class_id, uri);
+}
+
+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 rawImfilePopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!rawImfileSelectRowsFits(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",
+                RAWIMFILE_TABLE_NAME, RAWIMFILE_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 rawImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, RAWIMFILE_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, RAWIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", RAWIMFILE_INDEX_NAME);
+            psFree(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;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, object->exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class", 0, NULL, object->class)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
+        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 (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, object->uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+rawImfileRow *rawImfileObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *exp_id;
+    char            *class;
+    char            *class_id;
+    char            *uri;
+
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    class = psMetadataLookupPtr(&status, md, "class");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class");
+        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;
+    }
+    uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+
+    return rawImfileRowAlloc(exp_id, class, class_id, uri);
+}
+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;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, RAWIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", RAWIMFILE_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++) {
+        rawImfileRow *object = rawImfileObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+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;
+}
+static void p1PendingExpRowFree(p1PendingExpRow *object);
+
+p1PendingExpRow *p1PendingExpRowAlloc(const char *exp_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles, const char *filter, psF32 airmass, psF64 ra, psF64 decl, psF32 exp_time, psF64 background, const char *recipe, psS32 p1_version)
+{
+    p1PendingExpRow *object;
+
+    object = psAlloc(sizeof(p1PendingExpRow));
+    psMemSetDeallocator(object, (psFreeFunc)p1PendingExpRowFree);
+
+    object->exp_id = psStringCopy(exp_id);
+    object->camera = psStringCopy(camera);
+    object->telescope = psStringCopy(telescope);
+    object->exp_type = psStringCopy(exp_type);
+    object->imfiles = imfiles;
+    object->filter = psStringCopy(filter);
+    object->airmass = airmass;
+    object->ra = ra;
+    object->decl = decl;
+    object->exp_time = exp_time;
+    object->background = background;
+    object->recipe = psStringCopy(recipe);
+    object->p1_version = p1_version;
+
+    return object;
+}
+
+static void p1PendingExpRowFree(p1PendingExpRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->camera);
+    psFree(object->telescope);
+    psFree(object->exp_type);
+    psFree(object->filter);
+    psFree(object->recipe);
+}
+
+bool p1PendingExpCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, P1PENDINGEXP_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", P1PENDINGEXP_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exp_time", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "background", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item background");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
+        psFree(md);
+        return false;
+    }
+
+    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_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles, const char *filter, psF32 airmass, psF64 ra, psF64 decl, psF32 exp_time, psF64 background, const char *recipe, psS32 p1_version)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, exp_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, filter)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass", 0, NULL, airmass)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exp_time", 0, NULL, exp_time)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "background", 0, NULL, background)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item background");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, p1_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
+        psFree(md);
+        return false;
+    }
+
+    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 p1PendingExpPop(psDB *dbh, char **exp_id, char **camera, char **telescope, char **exp_type, psS32 *imfiles, char **filter, psF32 *airmass, psF64 *ra, psF64 *decl, psF32 *exp_time, psF64 *background, char **recipe, psS32 *p1_version)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, P1PENDINGEXP_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", P1PENDINGEXP_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, P1PENDINGEXP_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", P1PENDINGEXP_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, P1PENDINGEXP_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, P1PENDINGEXP_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);
+
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *camera = psMetadataLookupPtr(&status, row, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        psFree(row);
+        return false;
+    }
+    *telescope = psMetadataLookupPtr(&status, row, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        psFree(row);
+        return false;
+    }
+    *exp_type = psMetadataLookupPtr(&status, row, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        psFree(row);
+        return false;
+    }
+    *imfiles = psMetadataLookupS32(&status, row, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        psFree(row);
+        return false;
+    }
+    *filter = psMetadataLookupPtr(&status, row, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        psFree(row);
+        return false;
+    }
+    *airmass = psMetadataLookupF32(&status, row, "airmass");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item airmass");
+        psFree(row);
+        return false;
+    }
+    *ra = psMetadataLookupF64(&status, row, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        psFree(row);
+        return false;
+    }
+    *decl = psMetadataLookupF64(&status, row, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        psFree(row);
+        return false;
+    }
+    *exp_time = psMetadataLookupF32(&status, row, "exp_time");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_time");
+        psFree(row);
+        return false;
+    }
+    *background = psMetadataLookupF64(&status, row, "background");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item background");
+        psFree(row);
+        return false;
+    }
+    *recipe = psMetadataLookupPtr(&status, row, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        psFree(row);
+        return false;
+    }
+    *p1_version = psMetadataLookupS32(&status, row, "p1_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p1_version");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool p1PendingExpInsertObject(psDB *dbh, p1PendingExpRow *object)
+{
+    return p1PendingExpInsert(dbh, object->exp_id, object->camera, object->telescope, object->exp_type, object->imfiles, object->filter, object->airmass, object->ra, object->decl, object->exp_time, object->background, object->recipe, object->p1_version);
+}
+
+p1PendingExpRow *p1PendingExpPopObject(psDB *dbh)
+{
+    char            exp_id[256];
+    char            camera[256];
+    char            telescope[256];
+    char            exp_type[256];
+    psS32           imfiles;
+    char            filter[256];
+    psF32           airmass;
+    psF64           ra;
+    psF64           decl;
+    psF32           exp_time;
+    psF64           background;
+    char            recipe[256];
+    psS32           p1_version;
+
+    if (!p1PendingExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles, (char **)&filter, &airmass, &ra, &decl, &exp_time, &background, (char **)&recipe, &p1_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return p1PendingExpRowAlloc(exp_id, camera, telescope, exp_type, imfiles, filter, airmass, ra, decl, exp_time, background, recipe, p1_version);
+}
+
+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 p1PendingExpPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!p1PendingExpSelectRowsFits(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",
+                P1PENDINGEXP_TABLE_NAME, P1PENDINGEXP_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 p1PendingExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, P1PENDINGEXP_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, P1PENDINGEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", P1PENDINGEXP_INDEX_NAME);
+            psFree(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;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, object->exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, object->camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, object->telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, object->exp_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, object->imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, object->filter)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass", 0, NULL, object->airmass)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, object->ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, object->decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exp_time", 0, NULL, object->exp_time)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "background", 0, NULL, object->background)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item background");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, object->recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, object->p1_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+p1PendingExpRow *p1PendingExpObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+    char            *filter;
+    psF32           airmass;
+    psF64           ra;
+    psF64           decl;
+    psF32           exp_time;
+    psF64           background;
+    char            *recipe;
+    psS32           p1_version;
+
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    camera = psMetadataLookupPtr(&status, md, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        return false;
+    }
+    telescope = psMetadataLookupPtr(&status, md, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        return false;
+    }
+    exp_type = psMetadataLookupPtr(&status, md, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        return false;
+    }
+    imfiles = psMetadataLookupS32(&status, md, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        return false;
+    }
+    filter = psMetadataLookupPtr(&status, md, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        return false;
+    }
+    airmass = psMetadataLookupF32(&status, md, "airmass");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item airmass");
+        return false;
+    }
+    ra = psMetadataLookupF64(&status, md, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        return false;
+    }
+    decl = psMetadataLookupF64(&status, md, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        return false;
+    }
+    exp_time = psMetadataLookupF32(&status, md, "exp_time");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_time");
+        return false;
+    }
+    background = psMetadataLookupF64(&status, md, "background");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item background");
+        return false;
+    }
+    recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+    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_id, camera, telescope, exp_type, imfiles, filter, airmass, ra, decl, exp_time, background, 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;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, P1PENDINGEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", P1PENDINGEXP_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++) {
+        p1PendingExpRow *object = p1PendingExpObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+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;
+}
+static void p2PendingExpRowFree(p2PendingExpRow *object);
+
+p2PendingExpRow *p2PendingExpRowAlloc(const char *exp_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles, const char *filter, psF32 airmass, psF32 ra, psF64 decl, psF64 exp_time, psF64 background, const char *recipe, psS32 p1_version, psS32 p2_version)
+{
+    p2PendingExpRow *object;
+
+    object = psAlloc(sizeof(p2PendingExpRow));
+    psMemSetDeallocator(object, (psFreeFunc)p2PendingExpRowFree);
+
+    object->exp_id = psStringCopy(exp_id);
+    object->camera = psStringCopy(camera);
+    object->telescope = psStringCopy(telescope);
+    object->exp_type = psStringCopy(exp_type);
+    object->imfiles = imfiles;
+    object->filter = psStringCopy(filter);
+    object->airmass = airmass;
+    object->ra = ra;
+    object->decl = decl;
+    object->exp_time = exp_time;
+    object->background = background;
+    object->recipe = psStringCopy(recipe);
+    object->p1_version = p1_version;
+    object->p2_version = p2_version;
+
+    return object;
+}
+
+static void p2PendingExpRowFree(p2PendingExpRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->camera);
+    psFree(object->telescope);
+    psFree(object->exp_type);
+    psFree(object->filter);
+    psFree(object->recipe);
+}
+
+bool p2PendingExpCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, P2PENDINGEXP_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", P2PENDINGEXP_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "ra", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "exp_time", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "background", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item background");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
+        psFree(md);
+        return false;
+    }
+
+    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_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles, const char *filter, psF32 airmass, psF32 ra, psF64 decl, psF64 exp_time, psF64 background, const char *recipe, psS32 p1_version, psS32 p2_version)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, exp_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, filter)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass", 0, NULL, airmass)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "ra", 0, NULL, ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "exp_time", 0, NULL, exp_time)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "background", 0, NULL, background)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item background");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, p1_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, p2_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
+        psFree(md);
+        return false;
+    }
+
+    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 p2PendingExpPop(psDB *dbh, char **exp_id, char **camera, char **telescope, char **exp_type, psS32 *imfiles, char **filter, psF32 *airmass, psF32 *ra, psF64 *decl, psF64 *exp_time, psF64 *background, char **recipe, psS32 *p1_version, psS32 *p2_version)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, P2PENDINGEXP_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", P2PENDINGEXP_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, P2PENDINGEXP_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", P2PENDINGEXP_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, P2PENDINGEXP_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, P2PENDINGEXP_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);
+
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *camera = psMetadataLookupPtr(&status, row, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        psFree(row);
+        return false;
+    }
+    *telescope = psMetadataLookupPtr(&status, row, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        psFree(row);
+        return false;
+    }
+    *exp_type = psMetadataLookupPtr(&status, row, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        psFree(row);
+        return false;
+    }
+    *imfiles = psMetadataLookupS32(&status, row, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        psFree(row);
+        return false;
+    }
+    *filter = psMetadataLookupPtr(&status, row, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        psFree(row);
+        return false;
+    }
+    *airmass = psMetadataLookupF32(&status, row, "airmass");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item airmass");
+        psFree(row);
+        return false;
+    }
+    *ra = psMetadataLookupF32(&status, row, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        psFree(row);
+        return false;
+    }
+    *decl = psMetadataLookupF64(&status, row, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        psFree(row);
+        return false;
+    }
+    *exp_time = psMetadataLookupF64(&status, row, "exp_time");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_time");
+        psFree(row);
+        return false;
+    }
+    *background = psMetadataLookupF64(&status, row, "background");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item background");
+        psFree(row);
+        return false;
+    }
+    *recipe = psMetadataLookupPtr(&status, row, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        psFree(row);
+        return false;
+    }
+    *p1_version = psMetadataLookupS32(&status, row, "p1_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p1_version");
+        psFree(row);
+        return false;
+    }
+    *p2_version = psMetadataLookupS32(&status, row, "p2_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p2_version");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool p2PendingExpInsertObject(psDB *dbh, p2PendingExpRow *object)
+{
+    return p2PendingExpInsert(dbh, object->exp_id, object->camera, object->telescope, object->exp_type, object->imfiles, object->filter, object->airmass, object->ra, object->decl, object->exp_time, object->background, object->recipe, object->p1_version, object->p2_version);
+}
+
+p2PendingExpRow *p2PendingExpPopObject(psDB *dbh)
+{
+    char            exp_id[256];
+    char            camera[256];
+    char            telescope[256];
+    char            exp_type[256];
+    psS32           imfiles;
+    char            filter[256];
+    psF32           airmass;
+    psF32           ra;
+    psF64           decl;
+    psF64           exp_time;
+    psF64           background;
+    char            recipe[256];
+    psS32           p1_version;
+    psS32           p2_version;
+
+    if (!p2PendingExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles, (char **)&filter, &airmass, &ra, &decl, &exp_time, &background, (char **)&recipe, &p1_version, &p2_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return p2PendingExpRowAlloc(exp_id, camera, telescope, exp_type, imfiles, filter, airmass, ra, decl, exp_time, background, recipe, p1_version, p2_version);
+}
+
+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 p2PendingExpPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!p2PendingExpSelectRowsFits(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",
+                P2PENDINGEXP_TABLE_NAME, P2PENDINGEXP_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 p2PendingExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, P2PENDINGEXP_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, P2PENDINGEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", P2PENDINGEXP_INDEX_NAME);
+            psFree(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;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, object->exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, object->camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, object->telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, object->exp_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, object->imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, object->filter)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass", 0, NULL, object->airmass)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "ra", 0, NULL, object->ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, object->decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "exp_time", 0, NULL, object->exp_time)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "background", 0, NULL, object->background)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item background");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, object->recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, object->p1_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, object->p2_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+p2PendingExpRow *p2PendingExpObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+    char            *filter;
+    psF32           airmass;
+    psF32           ra;
+    psF64           decl;
+    psF64           exp_time;
+    psF64           background;
+    char            *recipe;
+    psS32           p1_version;
+    psS32           p2_version;
+
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    camera = psMetadataLookupPtr(&status, md, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        return false;
+    }
+    telescope = psMetadataLookupPtr(&status, md, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        return false;
+    }
+    exp_type = psMetadataLookupPtr(&status, md, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        return false;
+    }
+    imfiles = psMetadataLookupS32(&status, md, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        return false;
+    }
+    filter = psMetadataLookupPtr(&status, md, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        return false;
+    }
+    airmass = psMetadataLookupF32(&status, md, "airmass");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item airmass");
+        return false;
+    }
+    ra = psMetadataLookupF32(&status, md, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        return false;
+    }
+    decl = psMetadataLookupF64(&status, md, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        return false;
+    }
+    exp_time = psMetadataLookupF64(&status, md, "exp_time");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_time");
+        return false;
+    }
+    background = psMetadataLookupF64(&status, md, "background");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item background");
+        return false;
+    }
+    recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+    p1_version = psMetadataLookupS32(&status, md, "p1_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p1_version");
+        return false;
+    }
+    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 p2PendingExpRowAlloc(exp_id, camera, telescope, exp_type, imfiles, filter, airmass, ra, decl, exp_time, background, recipe, p1_version, p2_version);
+}
+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;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, P2PENDINGEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", P2PENDINGEXP_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++) {
+        p2PendingExpRow *object = p2PendingExpObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+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;
+}
+static void p2PendingImfileRowFree(p2PendingImfileRow *object);
+
+p2PendingImfileRow *p2PendingImfileRowAlloc(const char *exp_id, 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_id = psStringCopy(exp_id);
+    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_id);
+    psFree(object->class_id);
+    psFree(object->uri);
+    psFree(object->recipe);
+}
+
+bool p2PendingImfileCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, P2PENDINGIMFILE_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", P2PENDINGIMFILE_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
+        psFree(md);
+        return false;
+    }
+
+    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_id, const char *class_id, const char *uri, const char *recipe, psS32 p1_version, psS32 p2_version)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        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 (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, p1_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, p2_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
+        psFree(md);
+        return false;
+    }
+
+    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 p2PendingImfilePop(psDB *dbh, char **exp_id, char **class_id, char **uri, char **recipe, psS32 *p1_version, psS32 *p2_version)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, P2PENDINGIMFILE_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", P2PENDINGIMFILE_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, P2PENDINGIMFILE_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", P2PENDINGIMFILE_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, P2PENDINGIMFILE_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, P2PENDINGIMFILE_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);
+
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        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;
+    }
+    *uri = psMetadataLookupPtr(&status, row, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        psFree(row);
+        return false;
+    }
+    *recipe = psMetadataLookupPtr(&status, row, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        psFree(row);
+        return false;
+    }
+    *p1_version = psMetadataLookupS32(&status, row, "p1_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p1_version");
+        psFree(row);
+        return false;
+    }
+    *p2_version = psMetadataLookupS32(&status, row, "p2_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p2_version");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool p2PendingImfileInsertObject(psDB *dbh, p2PendingImfileRow *object)
+{
+    return p2PendingImfileInsert(dbh, object->exp_id, object->class_id, object->uri, object->recipe, object->p1_version, object->p2_version);
+}
+
+p2PendingImfileRow *p2PendingImfilePopObject(psDB *dbh)
+{
+    char            exp_id[256];
+    char            class_id[256];
+    char            uri[256];
+    char            recipe[256];
+    psS32           p1_version;
+    psS32           p2_version;
+
+    if (!p2PendingImfilePop(dbh, (char **)&exp_id, (char **)&class_id, (char **)&uri, (char **)&recipe, &p1_version, &p2_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return p2PendingImfileRowAlloc(exp_id, class_id, uri, recipe, p1_version, p2_version);
+}
+
+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 p2PendingImfilePopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!p2PendingImfileSelectRowsFits(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",
+                P2PENDINGIMFILE_TABLE_NAME, P2PENDINGIMFILE_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 p2PendingImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, P2PENDINGIMFILE_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, P2PENDINGIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", P2PENDINGIMFILE_INDEX_NAME);
+            psFree(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;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, object->exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        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 (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, object->uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, object->recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, object->p1_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, object->p2_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+p2PendingImfileRow *p2PendingImfileObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *exp_id;
+    char            *class_id;
+    char            *uri;
+    char            *recipe;
+    psS32           p1_version;
+    psS32           p2_version;
+
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        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;
+    }
+    uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+    recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+    p1_version = psMetadataLookupS32(&status, md, "p1_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p1_version");
+        return false;
+    }
+    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_id, 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;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, P2PENDINGIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", P2PENDINGIMFILE_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++) {
+        p2PendingImfileRow *object = p2PendingImfileObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+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;
+}
+static void p2DoneExpRowFree(p2DoneExpRow *object);
+
+p2DoneExpRow *p2DoneExpRowAlloc(const char *exp_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles, const char *filter, psF32 airmass, psF64 ra, psF64 decl, psF32 exp_time, psF64 background, const char *recipe, psS32 p1_version, psS32 p2_version)
+{
+    p2DoneExpRow    *object;
+
+    object = psAlloc(sizeof(p2DoneExpRow));
+    psMemSetDeallocator(object, (psFreeFunc)p2DoneExpRowFree);
+
+    object->exp_id = psStringCopy(exp_id);
+    object->camera = psStringCopy(camera);
+    object->telescope = psStringCopy(telescope);
+    object->exp_type = psStringCopy(exp_type);
+    object->imfiles = imfiles;
+    object->filter = psStringCopy(filter);
+    object->airmass = airmass;
+    object->ra = ra;
+    object->decl = decl;
+    object->exp_time = exp_time;
+    object->background = background;
+    object->recipe = psStringCopy(recipe);
+    object->p1_version = p1_version;
+    object->p2_version = p2_version;
+
+    return object;
+}
+
+static void p2DoneExpRowFree(p2DoneExpRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->camera);
+    psFree(object->telescope);
+    psFree(object->exp_type);
+    psFree(object->filter);
+    psFree(object->recipe);
+}
+
+bool p2DoneExpCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, P2DONEEXP_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", P2DONEEXP_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exp_time", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "background", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item background");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, P2DONEEXP_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool p2DoneExpDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, P2DONEEXP_TABLE_NAME);
+}
+
+bool p2DoneExpInsert(psDB * dbh, const char *exp_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles, const char *filter, psF32 airmass, psF64 ra, psF64 decl, psF32 exp_time, psF64 background, const char *recipe, psS32 p1_version, psS32 p2_version)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, exp_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, filter)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass", 0, NULL, airmass)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exp_time", 0, NULL, exp_time)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "background", 0, NULL, background)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item background");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, p1_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, p2_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, P2DONEEXP_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long p2DoneExpDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, P2DONEEXP_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from p2DoneExp");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool p2DoneExpPop(psDB *dbh, char **exp_id, char **camera, char **telescope, char **exp_type, psS32 *imfiles, char **filter, psF32 *airmass, psF64 *ra, psF64 *decl, psF32 *exp_time, psF64 *background, char **recipe, psS32 *p1_version, psS32 *p2_version)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, P2DONEEXP_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", P2DONEEXP_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, P2DONEEXP_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", P2DONEEXP_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, P2DONEEXP_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, P2DONEEXP_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);
+
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *camera = psMetadataLookupPtr(&status, row, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        psFree(row);
+        return false;
+    }
+    *telescope = psMetadataLookupPtr(&status, row, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        psFree(row);
+        return false;
+    }
+    *exp_type = psMetadataLookupPtr(&status, row, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        psFree(row);
+        return false;
+    }
+    *imfiles = psMetadataLookupS32(&status, row, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        psFree(row);
+        return false;
+    }
+    *filter = psMetadataLookupPtr(&status, row, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        psFree(row);
+        return false;
+    }
+    *airmass = psMetadataLookupF32(&status, row, "airmass");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item airmass");
+        psFree(row);
+        return false;
+    }
+    *ra = psMetadataLookupF64(&status, row, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        psFree(row);
+        return false;
+    }
+    *decl = psMetadataLookupF64(&status, row, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        psFree(row);
+        return false;
+    }
+    *exp_time = psMetadataLookupF32(&status, row, "exp_time");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_time");
+        psFree(row);
+        return false;
+    }
+    *background = psMetadataLookupF64(&status, row, "background");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item background");
+        psFree(row);
+        return false;
+    }
+    *recipe = psMetadataLookupPtr(&status, row, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        psFree(row);
+        return false;
+    }
+    *p1_version = psMetadataLookupS32(&status, row, "p1_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p1_version");
+        psFree(row);
+        return false;
+    }
+    *p2_version = psMetadataLookupS32(&status, row, "p2_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p2_version");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool p2DoneExpInsertObject(psDB *dbh, p2DoneExpRow *object)
+{
+    return p2DoneExpInsert(dbh, object->exp_id, object->camera, object->telescope, object->exp_type, object->imfiles, object->filter, object->airmass, object->ra, object->decl, object->exp_time, object->background, object->recipe, object->p1_version, object->p2_version);
+}
+
+p2DoneExpRow *p2DoneExpPopObject(psDB *dbh)
+{
+    char            exp_id[256];
+    char            camera[256];
+    char            telescope[256];
+    char            exp_type[256];
+    psS32           imfiles;
+    char            filter[256];
+    psF32           airmass;
+    psF64           ra;
+    psF64           decl;
+    psF32           exp_time;
+    psF64           background;
+    char            recipe[256];
+    psS32           p1_version;
+    psS32           p2_version;
+
+    if (!p2DoneExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles, (char **)&filter, &airmass, &ra, &decl, &exp_time, &background, (char **)&recipe, &p1_version, &p2_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return p2DoneExpRowAlloc(exp_id, camera, telescope, exp_type, imfiles, filter, airmass, ra, decl, exp_time, background, recipe, p1_version, p2_version);
+}
+
+bool p2DoneExpInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  P2DONEEXP_TABLE_NAME
+    if (!psFitsMoveExtName(fits, P2DONEEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", P2DONEEXP_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, P2DONEEXP_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool p2DoneExpPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!p2DoneExpSelectRowsFits(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",
+                P2DONEEXP_TABLE_NAME, P2DONEEXP_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 p2DoneExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, P2DONEEXP_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, P2DONEEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", P2DONEEXP_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, P2DONEEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *p2DoneExpMetadataFromObject(const p2DoneExpRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, object->exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, object->camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, object->telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, object->exp_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, object->imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, object->filter)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass", 0, NULL, object->airmass)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, object->ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, object->decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exp_time", 0, NULL, object->exp_time)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "background", 0, NULL, object->background)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item background");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, object->recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, object->p1_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, object->p2_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+p2DoneExpRow *p2DoneExpObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+    char            *filter;
+    psF32           airmass;
+    psF64           ra;
+    psF64           decl;
+    psF32           exp_time;
+    psF64           background;
+    char            *recipe;
+    psS32           p1_version;
+    psS32           p2_version;
+
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    camera = psMetadataLookupPtr(&status, md, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        return false;
+    }
+    telescope = psMetadataLookupPtr(&status, md, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        return false;
+    }
+    exp_type = psMetadataLookupPtr(&status, md, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        return false;
+    }
+    imfiles = psMetadataLookupS32(&status, md, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        return false;
+    }
+    filter = psMetadataLookupPtr(&status, md, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        return false;
+    }
+    airmass = psMetadataLookupF32(&status, md, "airmass");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item airmass");
+        return false;
+    }
+    ra = psMetadataLookupF64(&status, md, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        return false;
+    }
+    decl = psMetadataLookupF64(&status, md, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        return false;
+    }
+    exp_time = psMetadataLookupF32(&status, md, "exp_time");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_time");
+        return false;
+    }
+    background = psMetadataLookupF64(&status, md, "background");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item background");
+        return false;
+    }
+    recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+    p1_version = psMetadataLookupS32(&status, md, "p1_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p1_version");
+        return false;
+    }
+    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 p2DoneExpRowAlloc(exp_id, camera, telescope, exp_type, imfiles, filter, airmass, ra, decl, exp_time, background, recipe, p1_version, p2_version);
+}
+psArray *p2DoneExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, P2DONEEXP_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, P2DONEEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", P2DONEEXP_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++) {
+        p2DoneExpRow *object = p2DoneExpObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long p2DoneExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        p2DoneExpRow *object = objects->data[i];
+        psMetadata *where = p2DoneExpMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, P2DONEEXP_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from p2DoneExp");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void p2DoneImfileRowFree(p2DoneImfileRow *object);
+
+p2DoneImfileRow *p2DoneImfileRowAlloc(const char *exp_id, const char *class_id, const char *uri, const char *recipe, psS32 p1_version, psS32 p2_version)
+{
+    p2DoneImfileRow *object;
+
+    object = psAlloc(sizeof(p2DoneImfileRow));
+    psMemSetDeallocator(object, (psFreeFunc)p2DoneImfileRowFree);
+
+    object->exp_id = psStringCopy(exp_id);
+    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 p2DoneImfileRowFree(p2DoneImfileRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->class_id);
+    psFree(object->uri);
+    psFree(object->recipe);
+}
+
+bool p2DoneImfileCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, P2DONEIMFILE_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", P2DONEIMFILE_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, P2DONEIMFILE_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool p2DoneImfileDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, P2DONEIMFILE_TABLE_NAME);
+}
+
+bool p2DoneImfileInsert(psDB * dbh, const char *exp_id, const char *class_id, const char *uri, const char *recipe, psS32 p1_version, psS32 p2_version)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        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 (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, p1_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, p2_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, P2DONEIMFILE_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long p2DoneImfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, P2DONEIMFILE_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from p2DoneImfile");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool p2DoneImfilePop(psDB *dbh, char **exp_id, char **class_id, char **uri, char **recipe, psS32 *p1_version, psS32 *p2_version)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, P2DONEIMFILE_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", P2DONEIMFILE_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, P2DONEIMFILE_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", P2DONEIMFILE_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, P2DONEIMFILE_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, P2DONEIMFILE_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);
+
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        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;
+    }
+    *uri = psMetadataLookupPtr(&status, row, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        psFree(row);
+        return false;
+    }
+    *recipe = psMetadataLookupPtr(&status, row, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        psFree(row);
+        return false;
+    }
+    *p1_version = psMetadataLookupS32(&status, row, "p1_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p1_version");
+        psFree(row);
+        return false;
+    }
+    *p2_version = psMetadataLookupS32(&status, row, "p2_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p2_version");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool p2DoneImfileInsertObject(psDB *dbh, p2DoneImfileRow *object)
+{
+    return p2DoneImfileInsert(dbh, object->exp_id, object->class_id, object->uri, object->recipe, object->p1_version, object->p2_version);
+}
+
+p2DoneImfileRow *p2DoneImfilePopObject(psDB *dbh)
+{
+    char            exp_id[256];
+    char            class_id[256];
+    char            uri[256];
+    char            recipe[256];
+    psS32           p1_version;
+    psS32           p2_version;
+
+    if (!p2DoneImfilePop(dbh, (char **)&exp_id, (char **)&class_id, (char **)&uri, (char **)&recipe, &p1_version, &p2_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return p2DoneImfileRowAlloc(exp_id, class_id, uri, recipe, p1_version, p2_version);
+}
+
+bool p2DoneImfileInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  P2DONEIMFILE_TABLE_NAME
+    if (!psFitsMoveExtName(fits, P2DONEIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", P2DONEIMFILE_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, P2DONEIMFILE_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool p2DoneImfilePopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!p2DoneImfileSelectRowsFits(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",
+                P2DONEIMFILE_TABLE_NAME, P2DONEIMFILE_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 p2DoneImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, P2DONEIMFILE_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, P2DONEIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", P2DONEIMFILE_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, P2DONEIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *p2DoneImfileMetadataFromObject(const p2DoneImfileRow *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, object->exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        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 (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, object->uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, object->recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, object->p1_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, object->p2_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+p2DoneImfileRow *p2DoneImfileObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *exp_id;
+    char            *class_id;
+    char            *uri;
+    char            *recipe;
+    psS32           p1_version;
+    psS32           p2_version;
+
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        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;
+    }
+    uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+    recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+    p1_version = psMetadataLookupS32(&status, md, "p1_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p1_version");
+        return false;
+    }
+    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 p2DoneImfileRowAlloc(exp_id, class_id, uri, recipe, p1_version, p2_version);
+}
+psArray *p2DoneImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, P2DONEIMFILE_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, P2DONEIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", P2DONEIMFILE_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++) {
+        p2DoneImfileRow *object = p2DoneImfileObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long p2DoneImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        p2DoneImfileRow *object = objects->data[i];
+        psMetadata *where = p2DoneImfileMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, P2DONEIMFILE_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from p2DoneImfile");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void p3PendingExpRowFree(p3PendingExpRow *object);
+
+p3PendingExpRow *p3PendingExpRowAlloc(const char *exp_id, const char *camera, const char *exp_type, psS32 imfiles, const char *filter, psF32 airmass, psF64 ra, psF64 decl, psF32 exp_time, psF64 background, const char *recipe, psS32 p2_version, psS32 p3_version)
+{
+    p3PendingExpRow *object;
+
+    object = psAlloc(sizeof(p3PendingExpRow));
+    psMemSetDeallocator(object, (psFreeFunc)p3PendingExpRowFree);
+
+    object->exp_id = psStringCopy(exp_id);
+    object->camera = psStringCopy(camera);
+    object->exp_type = psStringCopy(exp_type);
+    object->imfiles = imfiles;
+    object->filter = psStringCopy(filter);
+    object->airmass = airmass;
+    object->ra = ra;
+    object->decl = decl;
+    object->exp_time = exp_time;
+    object->background = background;
+    object->recipe = psStringCopy(recipe);
+    object->p2_version = p2_version;
+    object->p3_version = p3_version;
+
+    return object;
+}
+
+static void p3PendingExpRowFree(p3PendingExpRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->camera);
+    psFree(object->exp_type);
+    psFree(object->filter);
+    psFree(object->recipe);
+}
+
+bool p3PendingExpCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, P3PENDINGEXP_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", P3PENDINGEXP_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exp_time", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "background", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item background");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p3_version", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p3_version");
+        psFree(md);
+        return false;
+    }
+
+    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_id, const char *camera, const char *exp_type, psS32 imfiles, const char *filter, psF32 airmass, psF64 ra, psF64 decl, psF32 exp_time, psF64 background, const char *recipe, psS32 p2_version, psS32 p3_version)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, exp_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, filter)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass", 0, NULL, airmass)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exp_time", 0, NULL, exp_time)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "background", 0, NULL, background)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item background");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, p2_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p3_version", 0, NULL, p3_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p3_version");
+        psFree(md);
+        return false;
+    }
+
+    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 p3PendingExpPop(psDB *dbh, char **exp_id, char **camera, char **exp_type, psS32 *imfiles, char **filter, psF32 *airmass, psF64 *ra, psF64 *decl, psF32 *exp_time, psF64 *background, char **recipe, psS32 *p2_version, psS32 *p3_version)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, P3PENDINGEXP_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", P3PENDINGEXP_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, P3PENDINGEXP_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", P3PENDINGEXP_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, P3PENDINGEXP_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, P3PENDINGEXP_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);
+
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *camera = psMetadataLookupPtr(&status, row, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        psFree(row);
+        return false;
+    }
+    *exp_type = psMetadataLookupPtr(&status, row, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        psFree(row);
+        return false;
+    }
+    *imfiles = psMetadataLookupS32(&status, row, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        psFree(row);
+        return false;
+    }
+    *filter = psMetadataLookupPtr(&status, row, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        psFree(row);
+        return false;
+    }
+    *airmass = psMetadataLookupF32(&status, row, "airmass");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item airmass");
+        psFree(row);
+        return false;
+    }
+    *ra = psMetadataLookupF64(&status, row, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        psFree(row);
+        return false;
+    }
+    *decl = psMetadataLookupF64(&status, row, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        psFree(row);
+        return false;
+    }
+    *exp_time = psMetadataLookupF32(&status, row, "exp_time");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_time");
+        psFree(row);
+        return false;
+    }
+    *background = psMetadataLookupF64(&status, row, "background");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item background");
+        psFree(row);
+        return false;
+    }
+    *recipe = psMetadataLookupPtr(&status, row, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        psFree(row);
+        return false;
+    }
+    *p2_version = psMetadataLookupS32(&status, row, "p2_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p2_version");
+        psFree(row);
+        return false;
+    }
+    *p3_version = psMetadataLookupS32(&status, row, "p3_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p3_version");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool p3PendingExpInsertObject(psDB *dbh, p3PendingExpRow *object)
+{
+    return p3PendingExpInsert(dbh, object->exp_id, object->camera, object->exp_type, object->imfiles, object->filter, object->airmass, object->ra, object->decl, object->exp_time, object->background, object->recipe, object->p2_version, object->p3_version);
+}
+
+p3PendingExpRow *p3PendingExpPopObject(psDB *dbh)
+{
+    char            exp_id[256];
+    char            camera[256];
+    char            exp_type[256];
+    psS32           imfiles;
+    char            filter[256];
+    psF32           airmass;
+    psF64           ra;
+    psF64           decl;
+    psF32           exp_time;
+    psF64           background;
+    char            recipe[256];
+    psS32           p2_version;
+    psS32           p3_version;
+
+    if (!p3PendingExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&exp_type, &imfiles, (char **)&filter, &airmass, &ra, &decl, &exp_time, &background, (char **)&recipe, &p2_version, &p3_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return p3PendingExpRowAlloc(exp_id, camera, exp_type, imfiles, filter, airmass, ra, decl, exp_time, background, recipe, p2_version, p3_version);
+}
+
+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 p3PendingExpPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!p3PendingExpSelectRowsFits(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",
+                P3PENDINGEXP_TABLE_NAME, P3PENDINGEXP_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 p3PendingExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, P3PENDINGEXP_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, P3PENDINGEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", P3PENDINGEXP_INDEX_NAME);
+            psFree(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;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, object->exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, object->camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, object->exp_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, object->imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, object->filter)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass", 0, NULL, object->airmass)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, object->ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, object->decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exp_time", 0, NULL, object->exp_time)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "background", 0, NULL, object->background)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item background");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, object->recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, object->p2_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "p3_version", 0, NULL, object->p3_version)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item p3_version");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+p3PendingExpRow *p3PendingExpObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    char            *exp_id;
+    char            *camera;
+    char            *exp_type;
+    psS32           imfiles;
+    char            *filter;
+    psF32           airmass;
+    psF64           ra;
+    psF64           decl;
+    psF32           exp_time;
+    psF64           background;
+    char            *recipe;
+    psS32           p2_version;
+    psS32           p3_version;
+
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    camera = psMetadataLookupPtr(&status, md, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        return false;
+    }
+    exp_type = psMetadataLookupPtr(&status, md, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        return false;
+    }
+    imfiles = psMetadataLookupS32(&status, md, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        return false;
+    }
+    filter = psMetadataLookupPtr(&status, md, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        return false;
+    }
+    airmass = psMetadataLookupF32(&status, md, "airmass");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item airmass");
+        return false;
+    }
+    ra = psMetadataLookupF64(&status, md, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        return false;
+    }
+    decl = psMetadataLookupF64(&status, md, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        return false;
+    }
+    exp_time = psMetadataLookupF32(&status, md, "exp_time");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_time");
+        return false;
+    }
+    background = psMetadataLookupF64(&status, md, "background");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item background");
+        return false;
+    }
+    recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+    p2_version = psMetadataLookupS32(&status, md, "p2_version");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p2_version");
+        return false;
+    }
+    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 p3PendingExpRowAlloc(exp_id, camera, exp_type, imfiles, filter, airmass, ra, decl, exp_time, background, recipe, p2_version, p3_version);
+}
+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;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, P3PENDINGEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", P3PENDINGEXP_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++) {
+        p3PendingExpRow *object = p3PendingExpObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+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;
+}
+static void detRunRowFree(detRunRow *object);
+
+detRunRow *detRunRowAlloc(psS32 iteration, const char *det_type)
+{
+    detRunRow       *object;
+
+    object = psAlloc(sizeof(detRunRow));
+    psMemSetDeallocator(object, (psFreeFunc)detRunRowFree);
+
+    object->iteration = iteration;
+    object->det_type = psStringCopy(det_type);
+
+    return object;
+}
+
+static void detRunRowFree(detRunRow *object)
+{
+    psFree(object->det_type);
+}
+
+bool detRunCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, DETRUN_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", DETRUN_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "det_type", 0, "Key", "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_type");
+        psFree(md);
+        return false;
+    }
+
+    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 iteration, const char *det_type)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    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, "det_type", 0, NULL, det_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_type");
+        psFree(md);
+        return false;
+    }
+
+    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 detRunPop(psDB *dbh, psS32 *iteration, char **det_type)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, DETRUN_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETRUN_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, DETRUN_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETRUN_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, DETRUN_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, DETRUN_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);
+
+    *iteration = psMetadataLookupS32(&status, row, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
+        psFree(row);
+        return false;
+    }
+    *det_type = psMetadataLookupPtr(&status, row, "det_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_type");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool detRunInsertObject(psDB *dbh, detRunRow *object)
+{
+    return detRunInsert(dbh, object->iteration, object->det_type);
+}
+
+detRunRow *detRunPopObject(psDB *dbh)
+{
+    psS32           iteration;
+    char            det_type[256];
+
+    if (!detRunPop(dbh, &iteration, (char **)&det_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return detRunRowAlloc(iteration, det_type);
+}
+
+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 detRunPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!detRunSelectRowsFits(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",
+                DETRUN_TABLE_NAME, DETRUN_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 detRunSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETRUN_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, DETRUN_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETRUN_INDEX_NAME);
+            psFree(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;
+
+    md = psMetadataAlloc();
+    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, "det_type", 0, NULL, object->det_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_type");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+detRunRow *detRunObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    psS32           iteration;
+    char            *det_type;
+
+    iteration = psMetadataLookupS32(&status, md, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
+        return false;
+    }
+    det_type = psMetadataLookupPtr(&status, md, "det_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_type");
+        return false;
+    }
+
+    return detRunRowAlloc(iteration, det_type);
+}
+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;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, DETRUN_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETRUN_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++) {
+        detRunRow *object = detRunObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+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;
+}
+static void detInputExpRowFree(detInputExpRow *object);
+
+detInputExpRow *detInputExpRowAlloc(psS32 det_id, psS32 iteration, const char *exp_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles, const char *filter, psF32 airmass, psF64 ra, psF64 decl, psF32 exp_time, psF64 background)
+{
+    detInputExpRow  *object;
+
+    object = psAlloc(sizeof(detInputExpRow));
+    psMemSetDeallocator(object, (psFreeFunc)detInputExpRowFree);
+
+    object->det_id = det_id;
+    object->iteration = iteration;
+    object->exp_id = psStringCopy(exp_id);
+    object->camera = psStringCopy(camera);
+    object->telescope = psStringCopy(telescope);
+    object->exp_type = psStringCopy(exp_type);
+    object->imfiles = imfiles;
+    object->filter = psStringCopy(filter);
+    object->airmass = airmass;
+    object->ra = ra;
+    object->decl = decl;
+    object->exp_time = exp_time;
+    object->background = background;
+
+    return object;
+}
+
+static void detInputExpRowFree(detInputExpRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->camera);
+    psFree(object->telescope);
+    psFree(object->exp_type);
+    psFree(object->filter);
+}
+
+bool detInputExpCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, DETINPUTEXP_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", DETINPUTEXP_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, "exp_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exp_time", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "background", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item background");
+        psFree(md);
+        return false;
+    }
+
+    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_id, const char *camera, const char *telescope, const char *exp_type, psS32 imfiles, const char *filter, psF32 airmass, psF64 ra, psF64 decl, psF32 exp_time, psF64 background)
+{
+    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, "exp_id", 0, NULL, exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, exp_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, filter)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass", 0, NULL, airmass)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exp_time", 0, NULL, exp_time)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "background", 0, NULL, background)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item background");
+        psFree(md);
+        return false;
+    }
+
+    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 detInputExpPop(psDB *dbh, psS32 *det_id, psS32 *iteration, char **exp_id, char **camera, char **telescope, char **exp_type, psS32 *imfiles, char **filter, psF32 *airmass, psF64 *ra, psF64 *decl, psF32 *exp_time, psF64 *background)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, DETINPUTEXP_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETINPUTEXP_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, DETINPUTEXP_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETINPUTEXP_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, DETINPUTEXP_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, DETINPUTEXP_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;
+    }
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *camera = psMetadataLookupPtr(&status, row, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        psFree(row);
+        return false;
+    }
+    *telescope = psMetadataLookupPtr(&status, row, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        psFree(row);
+        return false;
+    }
+    *exp_type = psMetadataLookupPtr(&status, row, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        psFree(row);
+        return false;
+    }
+    *imfiles = psMetadataLookupS32(&status, row, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        psFree(row);
+        return false;
+    }
+    *filter = psMetadataLookupPtr(&status, row, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        psFree(row);
+        return false;
+    }
+    *airmass = psMetadataLookupF32(&status, row, "airmass");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item airmass");
+        psFree(row);
+        return false;
+    }
+    *ra = psMetadataLookupF64(&status, row, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        psFree(row);
+        return false;
+    }
+    *decl = psMetadataLookupF64(&status, row, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        psFree(row);
+        return false;
+    }
+    *exp_time = psMetadataLookupF32(&status, row, "exp_time");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_time");
+        psFree(row);
+        return false;
+    }
+    *background = psMetadataLookupF64(&status, row, "background");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item background");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool detInputExpInsertObject(psDB *dbh, detInputExpRow *object)
+{
+    return detInputExpInsert(dbh, object->det_id, object->iteration, object->exp_id, object->camera, object->telescope, object->exp_type, object->imfiles, object->filter, object->airmass, object->ra, object->decl, object->exp_time, object->background);
+}
+
+detInputExpRow *detInputExpPopObject(psDB *dbh)
+{
+    psS32           det_id;
+    psS32           iteration;
+    char            exp_id[256];
+    char            camera[256];
+    char            telescope[256];
+    char            exp_type[256];
+    psS32           imfiles;
+    char            filter[256];
+    psF32           airmass;
+    psF64           ra;
+    psF64           decl;
+    psF32           exp_time;
+    psF64           background;
+
+    if (!detInputExpPop(dbh, &det_id, &iteration, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles, (char **)&filter, &airmass, &ra, &decl, &exp_time, &background)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return detInputExpRowAlloc(det_id, iteration, exp_id, camera, telescope, exp_type, imfiles, filter, airmass, ra, decl, exp_time, background);
+}
+
+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 detInputExpPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!detInputExpSelectRowsFits(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",
+                DETINPUTEXP_TABLE_NAME, DETINPUTEXP_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 detInputExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETINPUTEXP_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, DETINPUTEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETINPUTEXP_INDEX_NAME);
+            psFree(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;
+
+    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, "exp_id", 0, NULL, object->exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, object->camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, object->telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, object->exp_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, object->imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, object->filter)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass", 0, NULL, object->airmass)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, object->ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, object->decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "exp_time", 0, NULL, object->exp_time)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF64(md, PS_LIST_TAIL, "background", 0, NULL, object->background)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item background");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+detInputExpRow *detInputExpObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    psS32           det_id;
+    psS32           iteration;
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+    char            *filter;
+    psF32           airmass;
+    psF64           ra;
+    psF64           decl;
+    psF32           exp_time;
+    psF64           background;
+
+    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;
+    }
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    camera = psMetadataLookupPtr(&status, md, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        return false;
+    }
+    telescope = psMetadataLookupPtr(&status, md, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        return false;
+    }
+    exp_type = psMetadataLookupPtr(&status, md, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        return false;
+    }
+    imfiles = psMetadataLookupS32(&status, md, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        return false;
+    }
+    filter = psMetadataLookupPtr(&status, md, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        return false;
+    }
+    airmass = psMetadataLookupF32(&status, md, "airmass");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item airmass");
+        return false;
+    }
+    ra = psMetadataLookupF64(&status, md, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        return false;
+    }
+    decl = psMetadataLookupF64(&status, md, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        return false;
+    }
+    exp_time = psMetadataLookupF32(&status, md, "exp_time");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_time");
+        return false;
+    }
+    background = psMetadataLookupF64(&status, md, "background");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item background");
+        return false;
+    }
+
+    return detInputExpRowAlloc(det_id, iteration, exp_id, camera, telescope, exp_type, imfiles, filter, airmass, ra, decl, exp_time, background);
+}
+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;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, DETINPUTEXP_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETINPUTEXP_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++) {
+        detInputExpRow *object = detInputExpObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+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;
+}
+static void detProcessedImfileRowFree(detProcessedImfileRow *object);
+
+detProcessedImfileRow *detProcessedImfileRowAlloc(psS32 det_id, const char *exp_id, const char *class_id, const char *uri, const char *recipe)
+{
+    detProcessedImfileRow *object;
+
+    object = psAlloc(sizeof(detProcessedImfileRow));
+    psMemSetDeallocator(object, (psFreeFunc)detProcessedImfileRowFree);
+
+    object->det_id = det_id;
+    object->exp_id = psStringCopy(exp_id);
+    object->class_id = psStringCopy(class_id);
+    object->uri = psStringCopy(uri);
+    object->recipe = psStringCopy(recipe);
+
+    return object;
+}
+
+static void detProcessedImfileRowFree(detProcessedImfileRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->class_id);
+    psFree(object->uri);
+    psFree(object->recipe);
+}
+
+bool detProcessedImfileCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, DETPROCESSEDIMFILE_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", DETPROCESSEDIMFILE_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 (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        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 (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+
+    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_id, const char *class_id, const char *uri, const char *recipe)
+{
+    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 (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        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 (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+
+    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 detProcessedImfilePop(psDB *dbh, psS32 *det_id, char **exp_id, char **class_id, char **uri, char **recipe)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, DETPROCESSEDIMFILE_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETPROCESSEDIMFILE_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, DETPROCESSEDIMFILE_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETPROCESSEDIMFILE_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, DETPROCESSEDIMFILE_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, DETPROCESSEDIMFILE_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;
+    }
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        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;
+    }
+    *uri = psMetadataLookupPtr(&status, row, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        psFree(row);
+        return false;
+    }
+    *recipe = psMetadataLookupPtr(&status, row, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool detProcessedImfileInsertObject(psDB *dbh, detProcessedImfileRow *object)
+{
+    return detProcessedImfileInsert(dbh, object->det_id, object->exp_id, object->class_id, object->uri, object->recipe);
+}
+
+detProcessedImfileRow *detProcessedImfilePopObject(psDB *dbh)
+{
+    psS32           det_id;
+    char            exp_id[256];
+    char            class_id[256];
+    char            uri[256];
+    char            recipe[256];
+
+    if (!detProcessedImfilePop(dbh, &det_id, (char **)&exp_id, (char **)&class_id, (char **)&uri, (char **)&recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return detProcessedImfileRowAlloc(det_id, exp_id, class_id, uri, recipe);
+}
+
+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 detProcessedImfilePopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!detProcessedImfileSelectRowsFits(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",
+                DETPROCESSEDIMFILE_TABLE_NAME, DETPROCESSEDIMFILE_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 detProcessedImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETPROCESSEDIMFILE_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, DETPROCESSEDIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETPROCESSEDIMFILE_INDEX_NAME);
+            psFree(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;
+
+    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 (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, object->exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        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 (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, object->uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, object->recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+detProcessedImfileRow *detProcessedImfileObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    psS32           det_id;
+    char            *exp_id;
+    char            *class_id;
+    char            *uri;
+    char            *recipe;
+
+    det_id = psMetadataLookupS32(&status, md, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        return false;
+    }
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        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;
+    }
+    uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+    recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+
+    return detProcessedImfileRowAlloc(det_id, exp_id, class_id, uri, recipe);
+}
+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;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, DETPROCESSEDIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETPROCESSEDIMFILE_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++) {
+        detProcessedImfileRow *object = detProcessedImfileObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+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;
+}
+static void detStackedImfileRowFree(detStackedImfileRow *object);
+
+detStackedImfileRow *detStackedImfileRowAlloc(psS32 det_id, psS32 iteration, const char *class_id, const char *uri, const char *recipe)
+{
+    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);
+
+    return object;
+}
+
+static void detStackedImfileRowFree(detStackedImfileRow *object)
+{
+    psFree(object->class_id);
+    psFree(object->uri);
+    psFree(object->recipe);
+}
+
+bool detStackedImfileCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, DETSTACKEDIMFILE_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", DETSTACKEDIMFILE_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 (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+
+    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)
+{
+    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 (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+
+    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 detStackedImfilePop(psDB *dbh, psS32 *det_id, psS32 *iteration, char **class_id, char **uri, char **recipe)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, DETSTACKEDIMFILE_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETSTACKEDIMFILE_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, DETSTACKEDIMFILE_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETSTACKEDIMFILE_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, DETSTACKEDIMFILE_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, DETSTACKEDIMFILE_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;
+    }
+    *uri = psMetadataLookupPtr(&status, row, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        psFree(row);
+        return false;
+    }
+    *recipe = psMetadataLookupPtr(&status, row, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool detStackedImfileInsertObject(psDB *dbh, detStackedImfileRow *object)
+{
+    return detStackedImfileInsert(dbh, object->det_id, object->iteration, object->class_id, object->uri, object->recipe);
+}
+
+detStackedImfileRow *detStackedImfilePopObject(psDB *dbh)
+{
+    psS32           det_id;
+    psS32           iteration;
+    char            class_id[256];
+    char            uri[256];
+    char            recipe[256];
+
+    if (!detStackedImfilePop(dbh, &det_id, &iteration, (char **)&class_id, (char **)&uri, (char **)&recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return detStackedImfileRowAlloc(det_id, iteration, class_id, uri, recipe);
+}
+
+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 detStackedImfilePopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!detStackedImfileSelectRowsFits(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",
+                DETSTACKEDIMFILE_TABLE_NAME, DETSTACKEDIMFILE_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 detStackedImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETSTACKEDIMFILE_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, DETSTACKEDIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETSTACKEDIMFILE_INDEX_NAME);
+            psFree(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;
+
+    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 (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, object->uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, object->recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+detStackedImfileRow *detStackedImfileObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    psS32           det_id;
+    psS32           iteration;
+    char            *class_id;
+    char            *uri;
+    char            *recipe;
+
+    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;
+    }
+    uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+    recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+
+    return detStackedImfileRowAlloc(det_id, iteration, class_id, uri, recipe);
+}
+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;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, DETSTACKEDIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETSTACKEDIMFILE_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++) {
+        detStackedImfileRow *object = detStackedImfileObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+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;
+}
+static void detNormalizedImfileRowFree(detNormalizedImfileRow *object);
+
+detNormalizedImfileRow *detNormalizedImfileRowAlloc(psS32 det_id, psS32 iteration, const char *class_id, const char *uri, const char *recipe)
+{
+    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->recipe = psStringCopy(recipe);
+
+    return object;
+}
+
+static void detNormalizedImfileRowFree(detNormalizedImfileRow *object)
+{
+    psFree(object->class_id);
+    psFree(object->uri);
+    psFree(object->recipe);
+}
+
+bool detNormalizedImfileCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, DETNORMALIZEDIMFILE_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", DETNORMALIZEDIMFILE_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 (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+
+    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, const char *recipe)
+{
+    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 (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+
+    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 detNormalizedImfilePop(psDB *dbh, psS32 *det_id, psS32 *iteration, char **class_id, char **uri, char **recipe)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, DETNORMALIZEDIMFILE_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETNORMALIZEDIMFILE_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, DETNORMALIZEDIMFILE_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETNORMALIZEDIMFILE_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, DETNORMALIZEDIMFILE_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, DETNORMALIZEDIMFILE_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;
+    }
+    *uri = psMetadataLookupPtr(&status, row, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        psFree(row);
+        return false;
+    }
+    *recipe = psMetadataLookupPtr(&status, row, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool detNormalizedImfileInsertObject(psDB *dbh, detNormalizedImfileRow *object)
+{
+    return detNormalizedImfileInsert(dbh, object->det_id, object->iteration, object->class_id, object->uri, object->recipe);
+}
+
+detNormalizedImfileRow *detNormalizedImfilePopObject(psDB *dbh)
+{
+    psS32           det_id;
+    psS32           iteration;
+    char            class_id[256];
+    char            uri[256];
+    char            recipe[256];
+
+    if (!detNormalizedImfilePop(dbh, &det_id, &iteration, (char **)&class_id, (char **)&uri, (char **)&recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return detNormalizedImfileRowAlloc(det_id, iteration, class_id, uri, recipe);
+}
+
+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 detNormalizedImfilePopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!detNormalizedImfileSelectRowsFits(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",
+                DETNORMALIZEDIMFILE_TABLE_NAME, DETNORMALIZEDIMFILE_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 detNormalizedImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETNORMALIZEDIMFILE_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, DETNORMALIZEDIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETNORMALIZEDIMFILE_INDEX_NAME);
+            psFree(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;
+
+    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 (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, object->uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, object->recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+detNormalizedImfileRow *detNormalizedImfileObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    psS32           det_id;
+    psS32           iteration;
+    char            *class_id;
+    char            *uri;
+    char            *recipe;
+
+    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;
+    }
+    uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+    recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+
+    return detNormalizedImfileRowAlloc(det_id, iteration, class_id, uri, recipe);
+}
+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;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, DETNORMALIZEDIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETNORMALIZEDIMFILE_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++) {
+        detNormalizedImfileRow *object = detNormalizedImfileObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+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;
+}
+static void detMasterFrameRowFree(detMasterFrameRow *object);
+
+detMasterFrameRow *detMasterFrameRowAlloc(psS32 det_id, psS32 iteration, const char *comment)
+{
+    detMasterFrameRow *object;
+
+    object = psAlloc(sizeof(detMasterFrameRow));
+    psMemSetDeallocator(object, (psFreeFunc)detMasterFrameRowFree);
+
+    object->det_id = det_id;
+    object->iteration = iteration;
+    object->comment = psStringCopy(comment);
+
+    return object;
+}
+
+static void detMasterFrameRowFree(detMasterFrameRow *object)
+{
+    psFree(object->comment);
+}
+
+bool detMasterFrameCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, DETMASTERFRAME_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", DETMASTERFRAME_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, "comment", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item comment");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, DETMASTERFRAME_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool detMasterFrameDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DETMASTERFRAME_TABLE_NAME);
+}
+
+bool detMasterFrameInsert(psDB * dbh, psS32 det_id, psS32 iteration, const char *comment)
+{
+    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, "comment", 0, NULL, comment)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item comment");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, DETMASTERFRAME_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long detMasterFrameDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, DETMASTERFRAME_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detMasterFrame");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool detMasterFramePop(psDB *dbh, psS32 *det_id, psS32 *iteration, char **comment)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, DETMASTERFRAME_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETMASTERFRAME_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, DETMASTERFRAME_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETMASTERFRAME_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, DETMASTERFRAME_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, DETMASTERFRAME_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;
+    }
+    *comment = psMetadataLookupPtr(&status, row, "comment");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item comment");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool detMasterFrameInsertObject(psDB *dbh, detMasterFrameRow *object)
+{
+    return detMasterFrameInsert(dbh, object->det_id, object->iteration, object->comment);
+}
+
+detMasterFrameRow *detMasterFramePopObject(psDB *dbh)
+{
+    psS32           det_id;
+    psS32           iteration;
+    char            comment[256];
+
+    if (!detMasterFramePop(dbh, &det_id, &iteration, (char **)&comment)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return detMasterFrameRowAlloc(det_id, iteration, comment);
+}
+
+bool detMasterFrameInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DETMASTERFRAME_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DETMASTERFRAME_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETMASTERFRAME_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, DETMASTERFRAME_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool detMasterFramePopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!detMasterFrameSelectRowsFits(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",
+                DETMASTERFRAME_TABLE_NAME, DETMASTERFRAME_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 detMasterFrameSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETMASTERFRAME_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, DETMASTERFRAME_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETMASTERFRAME_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DETMASTERFRAME_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *detMasterFrameMetadataFromObject(const detMasterFrameRow *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, "comment", 0, NULL, object->comment)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item comment");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+detMasterFrameRow *detMasterFrameObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    psS32           det_id;
+    psS32           iteration;
+    char            *comment;
+
+    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;
+    }
+    comment = psMetadataLookupPtr(&status, md, "comment");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item comment");
+        return false;
+    }
+
+    return detMasterFrameRowAlloc(det_id, iteration, comment);
+}
+psArray *detMasterFrameSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETMASTERFRAME_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, DETMASTERFRAME_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETMASTERFRAME_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++) {
+        detMasterFrameRow *object = detMasterFrameObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long detMasterFrameDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        detMasterFrameRow *object = objects->data[i];
+        psMetadata *where = detMasterFrameMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DETMASTERFRAME_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detMasterFrame");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void detMasterImfileRowFree(detMasterImfileRow *object);
+
+detMasterImfileRow *detMasterImfileRowAlloc(psS32 det_id, const char *class_id, const char *uri, const char *recipe)
+{
+    detMasterImfileRow *object;
+
+    object = psAlloc(sizeof(detMasterImfileRow));
+    psMemSetDeallocator(object, (psFreeFunc)detMasterImfileRowFree);
+
+    object->det_id = det_id;
+    object->class_id = psStringCopy(class_id);
+    object->uri = psStringCopy(uri);
+    object->recipe = psStringCopy(recipe);
+
+    return object;
+}
+
+static void detMasterImfileRowFree(detMasterImfileRow *object)
+{
+    psFree(object->class_id);
+    psFree(object->uri);
+    psFree(object->recipe);
+}
+
+bool detMasterImfileCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, DETMASTERIMFILE_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", DETMASTERIMFILE_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 (!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 (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, DETMASTERIMFILE_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool detMasterImfileDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DETMASTERIMFILE_TABLE_NAME);
+}
+
+bool detMasterImfileInsert(psDB * dbh, psS32 det_id, const char *class_id, const char *uri, const char *recipe)
+{
+    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 (!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 (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, DETMASTERIMFILE_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long detMasterImfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, DETMASTERIMFILE_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detMasterImfile");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool detMasterImfilePop(psDB *dbh, psS32 *det_id, char **class_id, char **uri, char **recipe)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, DETMASTERIMFILE_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETMASTERIMFILE_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, DETMASTERIMFILE_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETMASTERIMFILE_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, DETMASTERIMFILE_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, DETMASTERIMFILE_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;
+    }
+    *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;
+    }
+    *uri = psMetadataLookupPtr(&status, row, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        psFree(row);
+        return false;
+    }
+    *recipe = psMetadataLookupPtr(&status, row, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool detMasterImfileInsertObject(psDB *dbh, detMasterImfileRow *object)
+{
+    return detMasterImfileInsert(dbh, object->det_id, object->class_id, object->uri, object->recipe);
+}
+
+detMasterImfileRow *detMasterImfilePopObject(psDB *dbh)
+{
+    psS32           det_id;
+    char            class_id[256];
+    char            uri[256];
+    char            recipe[256];
+
+    if (!detMasterImfilePop(dbh, &det_id, (char **)&class_id, (char **)&uri, (char **)&recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return detMasterImfileRowAlloc(det_id, class_id, uri, recipe);
+}
+
+bool detMasterImfileInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DETMASTERIMFILE_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DETMASTERIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETMASTERIMFILE_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, DETMASTERIMFILE_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool detMasterImfilePopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!detMasterImfileSelectRowsFits(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",
+                DETMASTERIMFILE_TABLE_NAME, DETMASTERIMFILE_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 detMasterImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETMASTERIMFILE_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, DETMASTERIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETMASTERIMFILE_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DETMASTERIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *detMasterImfileMetadataFromObject(const detMasterImfileRow *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 (!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 (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, object->uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, object->recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+detMasterImfileRow *detMasterImfileObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    psS32           det_id;
+    char            *class_id;
+    char            *uri;
+    char            *recipe;
+
+    det_id = psMetadataLookupS32(&status, md, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        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;
+    }
+    uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+    recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+
+    return detMasterImfileRowAlloc(det_id, class_id, uri, recipe);
+}
+psArray *detMasterImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETMASTERIMFILE_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, DETMASTERIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETMASTERIMFILE_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++) {
+        detMasterImfileRow *object = detMasterImfileObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long detMasterImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        detMasterImfileRow *object = objects->data[i];
+        psMetadata *where = detMasterImfileMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DETMASTERIMFILE_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detMasterImfile");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void detResidImfileAnalysisRowFree(detResidImfileAnalysisRow *object);
+
+detResidImfileAnalysisRow *detResidImfileAnalysisRowAlloc(psS32 det_id, psS32 iteration, const char *exp_id, const char *class_id, const char *recipe, const char *uri, const char *b1_uri, const char *b2_uri)
+{
+    detResidImfileAnalysisRow *object;
+
+    object = psAlloc(sizeof(detResidImfileAnalysisRow));
+    psMemSetDeallocator(object, (psFreeFunc)detResidImfileAnalysisRowFree);
+
+    object->det_id = det_id;
+    object->iteration = iteration;
+    object->exp_id = psStringCopy(exp_id);
+    object->class_id = psStringCopy(class_id);
+    object->recipe = psStringCopy(recipe);
+    object->uri = psStringCopy(uri);
+    object->b1_uri = psStringCopy(b1_uri);
+    object->b2_uri = psStringCopy(b2_uri);
+
+    return object;
+}
+
+static void detResidImfileAnalysisRowFree(detResidImfileAnalysisRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->class_id);
+    psFree(object->recipe);
+    psFree(object->uri);
+    psFree(object->b1_uri);
+    psFree(object->b2_uri);
+}
+
+bool detResidImfileAnalysisCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, DETRESIDIMFILEANALYSIS_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", DETRESIDIMFILEANALYSIS_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, "exp_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        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 (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "b1_uri", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item b1_uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "b2_uri", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item b2_uri");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, DETRESIDIMFILEANALYSIS_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool detResidImfileAnalysisDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DETRESIDIMFILEANALYSIS_TABLE_NAME);
+}
+
+bool detResidImfileAnalysisInsert(psDB * dbh, psS32 det_id, psS32 iteration, const char *exp_id, const char *class_id, const char *recipe, const char *uri, const char *b1_uri, const char *b2_uri)
+{
+    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, "exp_id", 0, NULL, exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        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 (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "b1_uri", 0, NULL, b1_uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item b1_uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "b2_uri", 0, NULL, b2_uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item b2_uri");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBInsertOneRow(dbh, DETRESIDIMFILEANALYSIS_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long detResidImfileAnalysisDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, DETRESIDIMFILEANALYSIS_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detResidImfileAnalysis");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool detResidImfileAnalysisPop(psDB *dbh, psS32 *det_id, psS32 *iteration, char **exp_id, char **class_id, char **recipe, char **uri, char **b1_uri, char **b2_uri)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, DETRESIDIMFILEANALYSIS_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETRESIDIMFILEANALYSIS_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, DETRESIDIMFILEANALYSIS_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETRESIDIMFILEANALYSIS_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, DETRESIDIMFILEANALYSIS_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, DETRESIDIMFILEANALYSIS_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;
+    }
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        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;
+    }
+    *recipe = psMetadataLookupPtr(&status, row, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        psFree(row);
+        return false;
+    }
+    *uri = psMetadataLookupPtr(&status, row, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        psFree(row);
+        return false;
+    }
+    *b1_uri = psMetadataLookupPtr(&status, row, "b1_uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item b1_uri");
+        psFree(row);
+        return false;
+    }
+    *b2_uri = psMetadataLookupPtr(&status, row, "b2_uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item b2_uri");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool detResidImfileAnalysisInsertObject(psDB *dbh, detResidImfileAnalysisRow *object)
+{
+    return detResidImfileAnalysisInsert(dbh, object->det_id, object->iteration, object->exp_id, object->class_id, object->recipe, object->uri, object->b1_uri, object->b2_uri);
+}
+
+detResidImfileAnalysisRow *detResidImfileAnalysisPopObject(psDB *dbh)
+{
+    psS32           det_id;
+    psS32           iteration;
+    char            exp_id[256];
+    char            class_id[256];
+    char            recipe[256];
+    char            uri[256];
+    char            b1_uri[256];
+    char            b2_uri[256];
+
+    if (!detResidImfileAnalysisPop(dbh, &det_id, &iteration, (char **)&exp_id, (char **)&class_id, (char **)&recipe, (char **)&uri, (char **)&b1_uri, (char **)&b2_uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return detResidImfileAnalysisRowAlloc(det_id, iteration, exp_id, class_id, recipe, uri, b1_uri, b2_uri);
+}
+
+bool detResidImfileAnalysisInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DETRESIDIMFILEANALYSIS_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DETRESIDIMFILEANALYSIS_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETRESIDIMFILEANALYSIS_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, DETRESIDIMFILEANALYSIS_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool detResidImfileAnalysisPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!detResidImfileAnalysisSelectRowsFits(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",
+                DETRESIDIMFILEANALYSIS_TABLE_NAME, DETRESIDIMFILEANALYSIS_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 detResidImfileAnalysisSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETRESIDIMFILEANALYSIS_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, DETRESIDIMFILEANALYSIS_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETRESIDIMFILEANALYSIS_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DETRESIDIMFILEANALYSIS_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *detResidImfileAnalysisMetadataFromObject(const detResidImfileAnalysisRow *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, "exp_id", 0, NULL, object->exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        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 (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, object->recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, object->uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "b1_uri", 0, NULL, object->b1_uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item b1_uri");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "b2_uri", 0, NULL, object->b2_uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item b2_uri");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+detResidImfileAnalysisRow *detResidImfileAnalysisObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    psS32           det_id;
+    psS32           iteration;
+    char            *exp_id;
+    char            *class_id;
+    char            *recipe;
+    char            *uri;
+    char            *b1_uri;
+    char            *b2_uri;
+
+    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;
+    }
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        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;
+    }
+    recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+    uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+    b1_uri = psMetadataLookupPtr(&status, md, "b1_uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item b1_uri");
+        return false;
+    }
+    b2_uri = psMetadataLookupPtr(&status, md, "b2_uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item b2_uri");
+        return false;
+    }
+
+    return detResidImfileAnalysisRowAlloc(det_id, iteration, exp_id, class_id, recipe, uri, b1_uri, b2_uri);
+}
+psArray *detResidImfileAnalysisSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETRESIDIMFILEANALYSIS_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, DETRESIDIMFILEANALYSIS_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETRESIDIMFILEANALYSIS_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++) {
+        detResidImfileAnalysisRow *object = detResidImfileAnalysisObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long detResidImfileAnalysisDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        detResidImfileAnalysisRow *object = objects->data[i];
+        psMetadata *where = detResidImfileAnalysisMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DETRESIDIMFILEANALYSIS_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detResidImfileAnalysis");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+static void detResidExpAnalysisRowFree(detResidExpAnalysisRow *object);
+
+detResidExpAnalysisRow *detResidExpAnalysisRowAlloc(psS32 det_id, psS32 iteration, const char *exp_id, const char *recipe, bool accept)
+{
+    detResidExpAnalysisRow *object;
+
+    object = psAlloc(sizeof(detResidExpAnalysisRow));
+    psMemSetDeallocator(object, (psFreeFunc)detResidExpAnalysisRowFree);
+
+    object->det_id = det_id;
+    object->iteration = iteration;
+    object->exp_id = psStringCopy(exp_id);
+    object->recipe = psStringCopy(recipe);
+    object->accept = accept;
+
+    return object;
+}
+
+static void detResidExpAnalysisRowFree(detResidExpAnalysisRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->recipe);
+}
+
+bool detResidExpAnalysisCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, DETRESIDEXPANALYSIS_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", DETRESIDEXPANALYSIS_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, "exp_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "accept", PS_DATA_BOOL, NULL, false)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item accept");
+        psFree(md);
+        return false;
+    }
+
+    status = psDBCreateTable(dbh, DETRESIDEXPANALYSIS_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool detResidExpAnalysisDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DETRESIDEXPANALYSIS_TABLE_NAME);
+}
+
+bool detResidExpAnalysisInsert(psDB * dbh, psS32 det_id, psS32 iteration, const char *exp_id, const char *recipe, bool accept)
+{
+    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, "exp_id", 0, NULL, exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        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;
+    }
+
+    status = psDBInsertOneRow(dbh, DETRESIDEXPANALYSIS_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long detResidExpAnalysisDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, DETRESIDEXPANALYSIS_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detResidExpAnalysis");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool detResidExpAnalysisPop(psDB *dbh, psS32 *det_id, psS32 *iteration, char **exp_id, char **recipe, bool *accept)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, DETRESIDEXPANALYSIS_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETRESIDEXPANALYSIS_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, DETRESIDEXPANALYSIS_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETRESIDEXPANALYSIS_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, DETRESIDEXPANALYSIS_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, DETRESIDEXPANALYSIS_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;
+    }
+    *exp_id = psMetadataLookupPtr(&status, row, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        psFree(row);
+        return false;
+    }
+    *recipe = psMetadataLookupPtr(&status, row, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        psFree(row);
+        return false;
+    }
+    *accept = psMetadataLookupBool(&status, row, "accept");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item accept");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool detResidExpAnalysisInsertObject(psDB *dbh, detResidExpAnalysisRow *object)
+{
+    return detResidExpAnalysisInsert(dbh, object->det_id, object->iteration, object->exp_id, object->recipe, object->accept);
+}
+
+detResidExpAnalysisRow *detResidExpAnalysisPopObject(psDB *dbh)
+{
+    psS32           det_id;
+    psS32           iteration;
+    char            exp_id[256];
+    char            recipe[256];
+    bool            accept;
+
+    if (!detResidExpAnalysisPop(dbh, &det_id, &iteration, (char **)&exp_id, (char **)&recipe, &accept)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return detResidExpAnalysisRowAlloc(det_id, iteration, exp_id, recipe, accept);
+}
+
+bool detResidExpAnalysisInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DETRESIDEXPANALYSIS_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DETRESIDEXPANALYSIS_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETRESIDEXPANALYSIS_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, DETRESIDEXPANALYSIS_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool detResidExpAnalysisPopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!detResidExpAnalysisSelectRowsFits(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",
+                DETRESIDEXPANALYSIS_TABLE_NAME, DETRESIDEXPANALYSIS_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 detResidExpAnalysisSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETRESIDEXPANALYSIS_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, DETRESIDEXPANALYSIS_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETRESIDEXPANALYSIS_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DETRESIDEXPANALYSIS_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *detResidExpAnalysisMetadataFromObject(const detResidExpAnalysisRow *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, "exp_id", 0, NULL, object->exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, object->recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return NULL;
+    }
+    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 NULL;
+    }
+
+    return md;
+}
+
+detResidExpAnalysisRow *detResidExpAnalysisObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    psS32           det_id;
+    psS32           iteration;
+    char            *exp_id;
+    char            *recipe;
+    bool            accept;
+
+    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;
+    }
+    exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+    accept = psMetadataLookupBool(&status, md, "accept");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item accept");
+        return false;
+    }
+
+    return detResidExpAnalysisRowAlloc(det_id, iteration, exp_id, recipe, accept);
+}
+psArray *detResidExpAnalysisSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETRESIDEXPANALYSIS_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, DETRESIDEXPANALYSIS_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETRESIDEXPANALYSIS_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++) {
+        detResidExpAnalysisRow *object = detResidExpAnalysisObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+long long detResidExpAnalysisDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        detResidExpAnalysisRow *object = objects->data[i];
+        psMetadata *where = detResidExpAnalysisMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DETRESIDEXPANALYSIS_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detResidExpAnalysis");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
Index: /branches/jh_branches/jhoblitt/ippdb/src/ippdb.h
===================================================================
--- /branches/jh_branches/jhoblitt/ippdb/src/ippdb.h	(revision 22254)
+++ /branches/jh_branches/jhoblitt/ippdb/src/ippdb.h	(revision 22254)
@@ -0,0 +1,6306 @@
+#ifndef IPPDB_H
+#define IPPDB_H 1
+
+#include <pslib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/// @addtogroup ippdb
+/// @{
+
+/** Opens a new database connection
+ *
+ *  @return A new psDB object if the database connection is successful or NULL
+ *  on failure.
+ */
+
+psDB *ippdbInit(
+    const char      *host,              ///< Database server hostname
+    const char      *user,              ///< Database username
+    const char      *passwd,            ///< Database password
+    const char      *dbname             ///< Database table.namespace
+);
+
+/** Closes a database connection
+ */
+
+void ippdbCleanup(
+    psDB            *dbh                ///< Database handle
+);
+
+/** weatherRow data structure
+ *
+ * Structure for representing a single row of weather table data.
+ */
+
+typedef struct {
+    psF32           temp01;
+    psF32           humi01;
+    psF32           temp02;
+    psF32           humi02;
+    psF32           temp03;
+    psF32           humi03;
+    psF32           pressure;
+} weatherRow;
+
+/** Creates a new weatherRow object
+ *
+ *  @return A new weatherRow object or NULL on failure.
+ */
+
+weatherRow *weatherRowAlloc(
+    psF32           temp01,
+    psF32           humi01,
+    psF32           temp02,
+    psF32           humi02,
+    psF32           temp03,
+    psF32           humi03,
+    psF32           pressure
+);
+
+/** Creates a new weather table
+ *
+ * @return true on success
+ */
+
+bool weatherCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a weather table
+ *
+ * @return true on success
+ */
+
+bool weatherDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool weatherInsert(
+    psDB            *dbh,               ///< Database handle
+    psF32           temp01,
+    psF32           humi01,
+    psF32           temp02,
+    psF32           humi02,
+    psF32           temp03,
+    psF32           humi03,
+    psF32           pressure
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long weatherDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool weatherPop(
+    psDB            *dbh,               ///< Database handle
+    psF32           *temp01,
+    psF32           *humi01,
+    psF32           *temp02,
+    psF32           *humi02,
+    psF32           *temp03,
+    psF32           *humi03,
+    psF32           *pressure
+);
+
+/** Insert a single weatherRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool weatherInsertObject(
+    psDB            *dbh,               ///< Database handle
+    weatherRow      *object             ///< weatherRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new weatherRow on success or NULL on failure.
+ */
+
+weatherRow *weatherPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table weatherRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool weatherInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool weatherPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool weatherSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a weatherRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *weatherMetadataFromObject(
+    const weatherRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A weatherRow pointer or NULL on error
+ */
+
+weatherRow *weatherObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as weatherRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *weatherSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long weatherDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** skyp_transparencyRow data structure
+ *
+ * Structure for representing a single row of skyp_transparency table data.
+ */
+
+typedef struct {
+    char            *filter;
+    psF64           trans;
+    psS32           nstars;
+    psF64           ra;
+    psF64           decl;
+    psF32           exptime;
+    psF64           sky_bright;
+} skyp_transparencyRow;
+
+/** Creates a new skyp_transparencyRow object
+ *
+ *  @return A new skyp_transparencyRow object or NULL on failure.
+ */
+
+skyp_transparencyRow *skyp_transparencyRowAlloc(
+    const char      *filter,
+    psF64           trans,
+    psS32           nstars,
+    psF64           ra,
+    psF64           decl,
+    psF32           exptime,
+    psF64           sky_bright
+);
+
+/** Creates a new skyp_transparency table
+ *
+ * @return true on success
+ */
+
+bool skyp_transparencyCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a skyp_transparency table
+ *
+ * @return true on success
+ */
+
+bool skyp_transparencyDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool skyp_transparencyInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *filter,
+    psF64           trans,
+    psS32           nstars,
+    psF64           ra,
+    psF64           decl,
+    psF32           exptime,
+    psF64           sky_bright
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long skyp_transparencyDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool skyp_transparencyPop(
+    psDB            *dbh,               ///< Database handle
+    char            **filter,
+    psF64           *trans,
+    psS32           *nstars,
+    psF64           *ra,
+    psF64           *decl,
+    psF32           *exptime,
+    psF64           *sky_bright
+);
+
+/** Insert a single skyp_transparencyRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool skyp_transparencyInsertObject(
+    psDB            *dbh,               ///< Database handle
+    skyp_transparencyRow *object             ///< skyp_transparencyRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new skyp_transparencyRow on success or NULL on failure.
+ */
+
+skyp_transparencyRow *skyp_transparencyPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table skyp_transparencyRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool skyp_transparencyInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool skyp_transparencyPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool skyp_transparencySelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a skyp_transparencyRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *skyp_transparencyMetadataFromObject(
+    const skyp_transparencyRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A skyp_transparencyRow pointer or NULL on error
+ */
+
+skyp_transparencyRow *skyp_transparencyObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as skyp_transparencyRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *skyp_transparencySelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long skyp_transparencyDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** skyp_absorptionRow data structure
+ *
+ * Structure for representing a single row of skyp_absorption table data.
+ */
+
+typedef struct {
+    char            *disperser_id;
+    psF32           atmcomp1;
+    psF32           atmcomp2;
+    psF32           atmcomp3;
+    psS32           nstars;
+    psF64           ra;
+    psF64           decl;
+    psF32           exptime;
+    psF64           sky_bright;
+} skyp_absorptionRow;
+
+/** Creates a new skyp_absorptionRow object
+ *
+ *  @return A new skyp_absorptionRow object or NULL on failure.
+ */
+
+skyp_absorptionRow *skyp_absorptionRowAlloc(
+    const char      *disperser_id,
+    psF32           atmcomp1,
+    psF32           atmcomp2,
+    psF32           atmcomp3,
+    psS32           nstars,
+    psF64           ra,
+    psF64           decl,
+    psF32           exptime,
+    psF64           sky_bright
+);
+
+/** Creates a new skyp_absorption table
+ *
+ * @return true on success
+ */
+
+bool skyp_absorptionCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a skyp_absorption table
+ *
+ * @return true on success
+ */
+
+bool skyp_absorptionDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool skyp_absorptionInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *disperser_id,
+    psF32           atmcomp1,
+    psF32           atmcomp2,
+    psF32           atmcomp3,
+    psS32           nstars,
+    psF64           ra,
+    psF64           decl,
+    psF32           exptime,
+    psF64           sky_bright
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long skyp_absorptionDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool skyp_absorptionPop(
+    psDB            *dbh,               ///< Database handle
+    char            **disperser_id,
+    psF32           *atmcomp1,
+    psF32           *atmcomp2,
+    psF32           *atmcomp3,
+    psS32           *nstars,
+    psF64           *ra,
+    psF64           *decl,
+    psF32           *exptime,
+    psF64           *sky_bright
+);
+
+/** Insert a single skyp_absorptionRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool skyp_absorptionInsertObject(
+    psDB            *dbh,               ///< Database handle
+    skyp_absorptionRow *object             ///< skyp_absorptionRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new skyp_absorptionRow on success or NULL on failure.
+ */
+
+skyp_absorptionRow *skyp_absorptionPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table skyp_absorptionRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool skyp_absorptionInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool skyp_absorptionPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool skyp_absorptionSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a skyp_absorptionRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *skyp_absorptionMetadataFromObject(
+    const skyp_absorptionRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A skyp_absorptionRow pointer or NULL on error
+ */
+
+skyp_absorptionRow *skyp_absorptionObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as skyp_absorptionRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *skyp_absorptionSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long skyp_absorptionDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** skyp_emissionRow data structure
+ *
+ * Structure for representing a single row of skyp_emission table data.
+ */
+
+typedef struct {
+    char            *disperser_id;
+    psF32           atmcomp1;
+    psF32           atmcomp2;
+    psF32           atmcomp3;
+    psF32           continuum;
+    psF32           exptime;
+} skyp_emissionRow;
+
+/** Creates a new skyp_emissionRow object
+ *
+ *  @return A new skyp_emissionRow object or NULL on failure.
+ */
+
+skyp_emissionRow *skyp_emissionRowAlloc(
+    const char      *disperser_id,
+    psF32           atmcomp1,
+    psF32           atmcomp2,
+    psF32           atmcomp3,
+    psF32           continuum,
+    psF32           exptime
+);
+
+/** Creates a new skyp_emission table
+ *
+ * @return true on success
+ */
+
+bool skyp_emissionCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a skyp_emission table
+ *
+ * @return true on success
+ */
+
+bool skyp_emissionDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool skyp_emissionInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *disperser_id,
+    psF32           atmcomp1,
+    psF32           atmcomp2,
+    psF32           atmcomp3,
+    psF32           continuum,
+    psF32           exptime
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long skyp_emissionDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool skyp_emissionPop(
+    psDB            *dbh,               ///< Database handle
+    char            **disperser_id,
+    psF32           *atmcomp1,
+    psF32           *atmcomp2,
+    psF32           *atmcomp3,
+    psF32           *continuum,
+    psF32           *exptime
+);
+
+/** Insert a single skyp_emissionRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool skyp_emissionInsertObject(
+    psDB            *dbh,               ///< Database handle
+    skyp_emissionRow *object             ///< skyp_emissionRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new skyp_emissionRow on success or NULL on failure.
+ */
+
+skyp_emissionRow *skyp_emissionPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table skyp_emissionRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool skyp_emissionInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool skyp_emissionPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool skyp_emissionSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a skyp_emissionRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *skyp_emissionMetadataFromObject(
+    const skyp_emissionRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A skyp_emissionRow pointer or NULL on error
+ */
+
+skyp_emissionRow *skyp_emissionObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as skyp_emissionRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *skyp_emissionSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long skyp_emissionDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** dimmRow data structure
+ *
+ * Structure for representing a single row of dimm table data.
+ */
+
+typedef struct {
+    psF32           sigmax;
+    psF32           sigmay;
+    psF32           fwhm;
+    psF64           ra;
+    psF64           decl;
+    psF32           expttime;
+    char            *telescope_id;
+} dimmRow;
+
+/** Creates a new dimmRow object
+ *
+ *  @return A new dimmRow object or NULL on failure.
+ */
+
+dimmRow *dimmRowAlloc(
+    psF32           sigmax,
+    psF32           sigmay,
+    psF32           fwhm,
+    psF64           ra,
+    psF64           decl,
+    psF32           expttime,
+    const char      *telescope_id
+);
+
+/** Creates a new dimm table
+ *
+ * @return true on success
+ */
+
+bool dimmCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a dimm table
+ *
+ * @return true on success
+ */
+
+bool dimmDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool dimmInsert(
+    psDB            *dbh,               ///< Database handle
+    psF32           sigmax,
+    psF32           sigmay,
+    psF32           fwhm,
+    psF64           ra,
+    psF64           decl,
+    psF32           expttime,
+    const char      *telescope_id
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long dimmDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool dimmPop(
+    psDB            *dbh,               ///< Database handle
+    psF32           *sigmax,
+    psF32           *sigmay,
+    psF32           *fwhm,
+    psF64           *ra,
+    psF64           *decl,
+    psF32           *expttime,
+    char            **telescope_id
+);
+
+/** Insert a single dimmRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool dimmInsertObject(
+    psDB            *dbh,               ///< Database handle
+    dimmRow         *object             ///< dimmRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new dimmRow on success or NULL on failure.
+ */
+
+dimmRow *dimmPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table dimmRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool dimmInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool dimmPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool dimmSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a dimmRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *dimmMetadataFromObject(
+    const dimmRow   *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A dimmRow pointer or NULL on error
+ */
+
+dimmRow *dimmObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as dimmRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *dimmSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long dimmDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** skyp_irRow data structure
+ *
+ * Structure for representing a single row of skyp_ir table data.
+ */
+
+typedef struct {
+    psF64           sky_bright;
+    psF64           sky_var;
+    psF64           ra;
+    psF64           decl;
+    psF32           fov_x;
+    psF32           fov_y;
+} skyp_irRow;
+
+/** Creates a new skyp_irRow object
+ *
+ *  @return A new skyp_irRow object or NULL on failure.
+ */
+
+skyp_irRow *skyp_irRowAlloc(
+    psF64           sky_bright,
+    psF64           sky_var,
+    psF64           ra,
+    psF64           decl,
+    psF32           fov_x,
+    psF32           fov_y
+);
+
+/** Creates a new skyp_ir table
+ *
+ * @return true on success
+ */
+
+bool skyp_irCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a skyp_ir table
+ *
+ * @return true on success
+ */
+
+bool skyp_irDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool skyp_irInsert(
+    psDB            *dbh,               ///< Database handle
+    psF64           sky_bright,
+    psF64           sky_var,
+    psF64           ra,
+    psF64           decl,
+    psF32           fov_x,
+    psF32           fov_y
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long skyp_irDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool skyp_irPop(
+    psDB            *dbh,               ///< Database handle
+    psF64           *sky_bright,
+    psF64           *sky_var,
+    psF64           *ra,
+    psF64           *decl,
+    psF32           *fov_x,
+    psF32           *fov_y
+);
+
+/** Insert a single skyp_irRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool skyp_irInsertObject(
+    psDB            *dbh,               ///< Database handle
+    skyp_irRow      *object             ///< skyp_irRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new skyp_irRow on success or NULL on failure.
+ */
+
+skyp_irRow *skyp_irPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table skyp_irRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool skyp_irInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool skyp_irPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool skyp_irSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a skyp_irRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *skyp_irMetadataFromObject(
+    const skyp_irRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A skyp_irRow pointer or NULL on error
+ */
+
+skyp_irRow *skyp_irObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as skyp_irRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *skyp_irSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long skyp_irDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** domeRow data structure
+ *
+ * Structure for representing a single row of dome table data.
+ */
+
+typedef struct {
+    psF32           az;
+    bool            open;
+    bool            light;
+    bool            track;
+} domeRow;
+
+/** Creates a new domeRow object
+ *
+ *  @return A new domeRow object or NULL on failure.
+ */
+
+domeRow *domeRowAlloc(
+    psF32           az,
+    bool            open,
+    bool            light,
+    bool            track
+);
+
+/** Creates a new dome table
+ *
+ * @return true on success
+ */
+
+bool domeCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a dome table
+ *
+ * @return true on success
+ */
+
+bool domeDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool domeInsert(
+    psDB            *dbh,               ///< Database handle
+    psF32           az,
+    bool            open,
+    bool            light,
+    bool            track
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long domeDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool domePop(
+    psDB            *dbh,               ///< Database handle
+    psF32           *az,
+    bool            *open,
+    bool            *light,
+    bool            *track
+);
+
+/** Insert a single domeRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool domeInsertObject(
+    psDB            *dbh,               ///< Database handle
+    domeRow         *object             ///< domeRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new domeRow on success or NULL on failure.
+ */
+
+domeRow *domePopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table domeRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool domeInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool domePopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool domeSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a domeRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *domeMetadataFromObject(
+    const domeRow   *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A domeRow pointer or NULL on error
+ */
+
+domeRow *domeObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as domeRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *domeSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long domeDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** telescopeRow data structure
+ *
+ * Structure for representing a single row of telescope table data.
+ */
+
+typedef struct {
+    char            *guide;
+    psF32           alt;
+    psF32           az;
+    psF64           ra;
+    psF64           decl;
+} telescopeRow;
+
+/** Creates a new telescopeRow object
+ *
+ *  @return A new telescopeRow object or NULL on failure.
+ */
+
+telescopeRow *telescopeRowAlloc(
+    const char      *guide,
+    psF32           alt,
+    psF32           az,
+    psF64           ra,
+    psF64           decl
+);
+
+/** Creates a new telescope table
+ *
+ * @return true on success
+ */
+
+bool telescopeCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a telescope table
+ *
+ * @return true on success
+ */
+
+bool telescopeDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool telescopeInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *guide,
+    psF32           alt,
+    psF32           az,
+    psF64           ra,
+    psF64           decl
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long telescopeDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool telescopePop(
+    psDB            *dbh,               ///< Database handle
+    char            **guide,
+    psF32           *alt,
+    psF32           *az,
+    psF64           *ra,
+    psF64           *decl
+);
+
+/** Insert a single telescopeRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool telescopeInsertObject(
+    psDB            *dbh,               ///< Database handle
+    telescopeRow    *object             ///< telescopeRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new telescopeRow on success or NULL on failure.
+ */
+
+telescopeRow *telescopePopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table telescopeRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool telescopeInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool telescopePopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool telescopeSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a telescopeRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *telescopeMetadataFromObject(
+    const telescopeRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A telescopeRow pointer or NULL on error
+ */
+
+telescopeRow *telescopeObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as telescopeRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *telescopeSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long telescopeDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** summitExpRow data structure
+ *
+ * Structure for representing a single row of summitExp table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    char            *uri;
+} summitExpRow;
+
+/** Creates a new summitExpRow object
+ *
+ *  @return A new summitExpRow object or NULL on failure.
+ */
+
+summitExpRow *summitExpRowAlloc(
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    const char      *uri
+);
+
+/** Creates a new summitExp table
+ *
+ * @return true on success
+ */
+
+bool summitExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a summitExp table
+ *
+ * @return true on success
+ */
+
+bool summitExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool summitExpInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    const char      *uri
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long summitExpDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool summitExpPop(
+    psDB            *dbh,               ///< Database handle
+    char            **exp_id,
+    char            **camera,
+    char            **telescope,
+    char            **exp_type,
+    char            **uri
+);
+
+/** Insert a single summitExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool summitExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    summitExpRow    *object             ///< summitExpRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new summitExpRow on success or NULL on failure.
+ */
+
+summitExpRow *summitExpPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table summitExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool summitExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool summitExpPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool summitExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a summitExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *summitExpMetadataFromObject(
+    const summitExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A summitExpRow pointer or NULL on error
+ */
+
+summitExpRow *summitExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as summitExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *summitExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long summitExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** pzPendingExpRow data structure
+ *
+ * Structure for representing a single row of pzPendingExp table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+} pzPendingExpRow;
+
+/** Creates a new pzPendingExpRow object
+ *
+ *  @return A new pzPendingExpRow object or NULL on failure.
+ */
+
+pzPendingExpRow *pzPendingExpRowAlloc(
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles
+);
+
+/** Creates a new pzPendingExp table
+ *
+ * @return true on success
+ */
+
+bool pzPendingExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a pzPendingExp table
+ *
+ * @return true on success
+ */
+
+bool pzPendingExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool pzPendingExpInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long pzPendingExpDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool pzPendingExpPop(
+    psDB            *dbh,               ///< Database handle
+    char            **exp_id,
+    char            **camera,
+    char            **telescope,
+    char            **exp_type,
+    psS32           *imfiles
+);
+
+/** Insert a single pzPendingExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool pzPendingExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    pzPendingExpRow *object             ///< pzPendingExpRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new pzPendingExpRow on success or NULL on failure.
+ */
+
+pzPendingExpRow *pzPendingExpPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table pzPendingExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool pzPendingExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool pzPendingExpPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool pzPendingExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a pzPendingExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *pzPendingExpMetadataFromObject(
+    const pzPendingExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A pzPendingExpRow pointer or NULL on error
+ */
+
+pzPendingExpRow *pzPendingExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as pzPendingExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *pzPendingExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long pzPendingExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** pzPendingImfileRow data structure
+ *
+ * Structure for representing a single row of pzPendingImfile table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    psS32           bytes;
+    char            *md5sum;
+    char            *class;
+    char            *class_id;
+    char            *uri;
+} pzPendingImfileRow;
+
+/** Creates a new pzPendingImfileRow object
+ *
+ *  @return A new pzPendingImfileRow object or NULL on failure.
+ */
+
+pzPendingImfileRow *pzPendingImfileRowAlloc(
+    const char      *exp_id,
+    psS32           bytes,
+    const char      *md5sum,
+    const char      *class,
+    const char      *class_id,
+    const char      *uri
+);
+
+/** Creates a new pzPendingImfile table
+ *
+ * @return true on success
+ */
+
+bool pzPendingImfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a pzPendingImfile table
+ *
+ * @return true on success
+ */
+
+bool pzPendingImfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool pzPendingImfileInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    psS32           bytes,
+    const char      *md5sum,
+    const char      *class,
+    const char      *class_id,
+    const char      *uri
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long pzPendingImfileDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool pzPendingImfilePop(
+    psDB            *dbh,               ///< Database handle
+    char            **exp_id,
+    psS32           *bytes,
+    char            **md5sum,
+    char            **class,
+    char            **class_id,
+    char            **uri
+);
+
+/** Insert a single pzPendingImfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool pzPendingImfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    pzPendingImfileRow *object             ///< pzPendingImfileRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new pzPendingImfileRow on success or NULL on failure.
+ */
+
+pzPendingImfileRow *pzPendingImfilePopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table pzPendingImfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool pzPendingImfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool pzPendingImfilePopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool pzPendingImfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a pzPendingImfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *pzPendingImfileMetadataFromObject(
+    const pzPendingImfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A pzPendingImfileRow pointer or NULL on error
+ */
+
+pzPendingImfileRow *pzPendingImfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as pzPendingImfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *pzPendingImfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long pzPendingImfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** newExpRow data structure
+ *
+ * Structure for representing a single row of newExp table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+} newExpRow;
+
+/** Creates a new newExpRow object
+ *
+ *  @return A new newExpRow object or NULL on failure.
+ */
+
+newExpRow *newExpRowAlloc(
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles
+);
+
+/** Creates a new newExp table
+ *
+ * @return true on success
+ */
+
+bool newExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a newExp table
+ *
+ * @return true on success
+ */
+
+bool newExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool newExpInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long newExpDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool newExpPop(
+    psDB            *dbh,               ///< Database handle
+    char            **exp_id,
+    char            **camera,
+    char            **telescope,
+    char            **exp_type,
+    psS32           *imfiles
+);
+
+/** Insert a single newExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool newExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    newExpRow       *object             ///< newExpRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new newExpRow on success or NULL on failure.
+ */
+
+newExpRow *newExpPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table newExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool newExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool newExpPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool newExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a newExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *newExpMetadataFromObject(
+    const newExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A newExpRow pointer or NULL on error
+ */
+
+newExpRow *newExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as newExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *newExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long newExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** newImfileRow data structure
+ *
+ * Structure for representing a single row of newImfile table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *class;
+    char            *class_id;
+    char            *uri;
+} newImfileRow;
+
+/** Creates a new newImfileRow object
+ *
+ *  @return A new newImfileRow object or NULL on failure.
+ */
+
+newImfileRow *newImfileRowAlloc(
+    const char      *exp_id,
+    const char      *class,
+    const char      *class_id,
+    const char      *uri
+);
+
+/** Creates a new newImfile table
+ *
+ * @return true on success
+ */
+
+bool newImfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a newImfile table
+ *
+ * @return true on success
+ */
+
+bool newImfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool newImfileInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *class,
+    const char      *class_id,
+    const char      *uri
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long newImfileDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool newImfilePop(
+    psDB            *dbh,               ///< Database handle
+    char            **exp_id,
+    char            **class,
+    char            **class_id,
+    char            **uri
+);
+
+/** Insert a single newImfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool newImfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    newImfileRow    *object             ///< newImfileRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new newImfileRow on success or NULL on failure.
+ */
+
+newImfileRow *newImfilePopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table newImfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool newImfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool newImfilePopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool newImfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a newImfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *newImfileMetadataFromObject(
+    const newImfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A newImfileRow pointer or NULL on error
+ */
+
+newImfileRow *newImfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as newImfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *newImfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long newImfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** rawDetrendExpRow data structure
+ *
+ * Structure for representing a single row of rawDetrendExp table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+    char            *filter;
+    psF32           airmass;
+    psF64           ra;
+    psF64           decl;
+    psF32           exp_time;
+    psF64           background;
+} rawDetrendExpRow;
+
+/** Creates a new rawDetrendExpRow object
+ *
+ *  @return A new rawDetrendExpRow object or NULL on failure.
+ */
+
+rawDetrendExpRow *rawDetrendExpRowAlloc(
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles,
+    const char      *filter,
+    psF32           airmass,
+    psF64           ra,
+    psF64           decl,
+    psF32           exp_time,
+    psF64           background
+);
+
+/** Creates a new rawDetrendExp table
+ *
+ * @return true on success
+ */
+
+bool rawDetrendExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a rawDetrendExp table
+ *
+ * @return true on success
+ */
+
+bool rawDetrendExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool rawDetrendExpInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles,
+    const char      *filter,
+    psF32           airmass,
+    psF64           ra,
+    psF64           decl,
+    psF32           exp_time,
+    psF64           background
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long rawDetrendExpDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool rawDetrendExpPop(
+    psDB            *dbh,               ///< Database handle
+    char            **exp_id,
+    char            **camera,
+    char            **telescope,
+    char            **exp_type,
+    psS32           *imfiles,
+    char            **filter,
+    psF32           *airmass,
+    psF64           *ra,
+    psF64           *decl,
+    psF32           *exp_time,
+    psF64           *background
+);
+
+/** Insert a single rawDetrendExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool rawDetrendExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    rawDetrendExpRow *object             ///< rawDetrendExpRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new rawDetrendExpRow on success or NULL on failure.
+ */
+
+rawDetrendExpRow *rawDetrendExpPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table rawDetrendExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool rawDetrendExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool rawDetrendExpPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool rawDetrendExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a rawDetrendExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *rawDetrendExpMetadataFromObject(
+    const rawDetrendExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A rawDetrendExpRow pointer or NULL on error
+ */
+
+rawDetrendExpRow *rawDetrendExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as rawDetrendExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *rawDetrendExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long rawDetrendExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** rawScienceExpRow data structure
+ *
+ * Structure for representing a single row of rawScienceExp table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+    char            *filter;
+    psF32           airmass;
+    psF64           ra;
+    psF64           decl;
+    psF32           exp_time;
+    psF64           background;
+} rawScienceExpRow;
+
+/** Creates a new rawScienceExpRow object
+ *
+ *  @return A new rawScienceExpRow object or NULL on failure.
+ */
+
+rawScienceExpRow *rawScienceExpRowAlloc(
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles,
+    const char      *filter,
+    psF32           airmass,
+    psF64           ra,
+    psF64           decl,
+    psF32           exp_time,
+    psF64           background
+);
+
+/** Creates a new rawScienceExp table
+ *
+ * @return true on success
+ */
+
+bool rawScienceExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a rawScienceExp table
+ *
+ * @return true on success
+ */
+
+bool rawScienceExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool rawScienceExpInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles,
+    const char      *filter,
+    psF32           airmass,
+    psF64           ra,
+    psF64           decl,
+    psF32           exp_time,
+    psF64           background
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long rawScienceExpDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool rawScienceExpPop(
+    psDB            *dbh,               ///< Database handle
+    char            **exp_id,
+    char            **camera,
+    char            **telescope,
+    char            **exp_type,
+    psS32           *imfiles,
+    char            **filter,
+    psF32           *airmass,
+    psF64           *ra,
+    psF64           *decl,
+    psF32           *exp_time,
+    psF64           *background
+);
+
+/** Insert a single rawScienceExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool rawScienceExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    rawScienceExpRow *object             ///< rawScienceExpRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new rawScienceExpRow on success or NULL on failure.
+ */
+
+rawScienceExpRow *rawScienceExpPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table rawScienceExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool rawScienceExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool rawScienceExpPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool rawScienceExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a rawScienceExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *rawScienceExpMetadataFromObject(
+    const rawScienceExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A rawScienceExpRow pointer or NULL on error
+ */
+
+rawScienceExpRow *rawScienceExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as rawScienceExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *rawScienceExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long rawScienceExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** rawImfileRow data structure
+ *
+ * Structure for representing a single row of rawImfile table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *class;
+    char            *class_id;
+    char            *uri;
+} rawImfileRow;
+
+/** Creates a new rawImfileRow object
+ *
+ *  @return A new rawImfileRow object or NULL on failure.
+ */
+
+rawImfileRow *rawImfileRowAlloc(
+    const char      *exp_id,
+    const char      *class,
+    const char      *class_id,
+    const char      *uri
+);
+
+/** Creates a new rawImfile table
+ *
+ * @return true on success
+ */
+
+bool rawImfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a rawImfile table
+ *
+ * @return true on success
+ */
+
+bool rawImfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool rawImfileInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *class,
+    const char      *class_id,
+    const char      *uri
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long rawImfileDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool rawImfilePop(
+    psDB            *dbh,               ///< Database handle
+    char            **exp_id,
+    char            **class,
+    char            **class_id,
+    char            **uri
+);
+
+/** Insert a single rawImfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool rawImfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    rawImfileRow    *object             ///< rawImfileRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new rawImfileRow on success or NULL on failure.
+ */
+
+rawImfileRow *rawImfilePopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table rawImfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool rawImfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool rawImfilePopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool rawImfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a rawImfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *rawImfileMetadataFromObject(
+    const rawImfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A rawImfileRow pointer or NULL on error
+ */
+
+rawImfileRow *rawImfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as rawImfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *rawImfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long rawImfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** p1PendingExpRow data structure
+ *
+ * Structure for representing a single row of p1PendingExp table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+    char            *filter;
+    psF32           airmass;
+    psF64           ra;
+    psF64           decl;
+    psF32           exp_time;
+    psF64           background;
+    char            *recipe;
+    psS32           p1_version;
+} p1PendingExpRow;
+
+/** Creates a new p1PendingExpRow object
+ *
+ *  @return A new p1PendingExpRow object or NULL on failure.
+ */
+
+p1PendingExpRow *p1PendingExpRowAlloc(
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles,
+    const char      *filter,
+    psF32           airmass,
+    psF64           ra,
+    psF64           decl,
+    psF32           exp_time,
+    psF64           background,
+    const char      *recipe,
+    psS32           p1_version
+);
+
+/** Creates a new p1PendingExp table
+ *
+ * @return true on success
+ */
+
+bool p1PendingExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a p1PendingExp table
+ *
+ * @return true on success
+ */
+
+bool p1PendingExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool p1PendingExpInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles,
+    const char      *filter,
+    psF32           airmass,
+    psF64           ra,
+    psF64           decl,
+    psF32           exp_time,
+    psF64           background,
+    const char      *recipe,
+    psS32           p1_version
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long p1PendingExpDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool p1PendingExpPop(
+    psDB            *dbh,               ///< Database handle
+    char            **exp_id,
+    char            **camera,
+    char            **telescope,
+    char            **exp_type,
+    psS32           *imfiles,
+    char            **filter,
+    psF32           *airmass,
+    psF64           *ra,
+    psF64           *decl,
+    psF32           *exp_time,
+    psF64           *background,
+    char            **recipe,
+    psS32           *p1_version
+);
+
+/** Insert a single p1PendingExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool p1PendingExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    p1PendingExpRow *object             ///< p1PendingExpRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new p1PendingExpRow on success or NULL on failure.
+ */
+
+p1PendingExpRow *p1PendingExpPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table p1PendingExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool p1PendingExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool p1PendingExpPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool p1PendingExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a p1PendingExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *p1PendingExpMetadataFromObject(
+    const p1PendingExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A p1PendingExpRow pointer or NULL on error
+ */
+
+p1PendingExpRow *p1PendingExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as p1PendingExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *p1PendingExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long p1PendingExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** p2PendingExpRow data structure
+ *
+ * Structure for representing a single row of p2PendingExp table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+    char            *filter;
+    psF32           airmass;
+    psF32           ra;
+    psF64           decl;
+    psF64           exp_time;
+    psF64           background;
+    char            *recipe;
+    psS32           p1_version;
+    psS32           p2_version;
+} p2PendingExpRow;
+
+/** Creates a new p2PendingExpRow object
+ *
+ *  @return A new p2PendingExpRow object or NULL on failure.
+ */
+
+p2PendingExpRow *p2PendingExpRowAlloc(
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles,
+    const char      *filter,
+    psF32           airmass,
+    psF32           ra,
+    psF64           decl,
+    psF64           exp_time,
+    psF64           background,
+    const char      *recipe,
+    psS32           p1_version,
+    psS32           p2_version
+);
+
+/** Creates a new p2PendingExp table
+ *
+ * @return true on success
+ */
+
+bool p2PendingExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a p2PendingExp table
+ *
+ * @return true on success
+ */
+
+bool p2PendingExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool p2PendingExpInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles,
+    const char      *filter,
+    psF32           airmass,
+    psF32           ra,
+    psF64           decl,
+    psF64           exp_time,
+    psF64           background,
+    const char      *recipe,
+    psS32           p1_version,
+    psS32           p2_version
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long p2PendingExpDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool p2PendingExpPop(
+    psDB            *dbh,               ///< Database handle
+    char            **exp_id,
+    char            **camera,
+    char            **telescope,
+    char            **exp_type,
+    psS32           *imfiles,
+    char            **filter,
+    psF32           *airmass,
+    psF32           *ra,
+    psF64           *decl,
+    psF64           *exp_time,
+    psF64           *background,
+    char            **recipe,
+    psS32           *p1_version,
+    psS32           *p2_version
+);
+
+/** Insert a single p2PendingExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool p2PendingExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    p2PendingExpRow *object             ///< p2PendingExpRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new p2PendingExpRow on success or NULL on failure.
+ */
+
+p2PendingExpRow *p2PendingExpPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table p2PendingExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool p2PendingExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool p2PendingExpPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool p2PendingExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a p2PendingExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *p2PendingExpMetadataFromObject(
+    const p2PendingExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A p2PendingExpRow pointer or NULL on error
+ */
+
+p2PendingExpRow *p2PendingExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as p2PendingExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *p2PendingExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long p2PendingExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** p2PendingImfileRow data structure
+ *
+ * Structure for representing a single row of p2PendingImfile table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *class_id;
+    char            *uri;
+    char            *recipe;
+    psS32           p1_version;
+    psS32           p2_version;
+} p2PendingImfileRow;
+
+/** Creates a new p2PendingImfileRow object
+ *
+ *  @return A new p2PendingImfileRow object or NULL on failure.
+ */
+
+p2PendingImfileRow *p2PendingImfileRowAlloc(
+    const char      *exp_id,
+    const char      *class_id,
+    const char      *uri,
+    const char      *recipe,
+    psS32           p1_version,
+    psS32           p2_version
+);
+
+/** Creates a new p2PendingImfile table
+ *
+ * @return true on success
+ */
+
+bool p2PendingImfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a p2PendingImfile table
+ *
+ * @return true on success
+ */
+
+bool p2PendingImfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool p2PendingImfileInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *class_id,
+    const char      *uri,
+    const char      *recipe,
+    psS32           p1_version,
+    psS32           p2_version
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long p2PendingImfileDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool p2PendingImfilePop(
+    psDB            *dbh,               ///< Database handle
+    char            **exp_id,
+    char            **class_id,
+    char            **uri,
+    char            **recipe,
+    psS32           *p1_version,
+    psS32           *p2_version
+);
+
+/** Insert a single p2PendingImfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool p2PendingImfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    p2PendingImfileRow *object             ///< p2PendingImfileRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new p2PendingImfileRow on success or NULL on failure.
+ */
+
+p2PendingImfileRow *p2PendingImfilePopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table p2PendingImfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool p2PendingImfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool p2PendingImfilePopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool p2PendingImfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a p2PendingImfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *p2PendingImfileMetadataFromObject(
+    const p2PendingImfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A p2PendingImfileRow pointer or NULL on error
+ */
+
+p2PendingImfileRow *p2PendingImfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as p2PendingImfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *p2PendingImfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long p2PendingImfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** p2DoneExpRow data structure
+ *
+ * Structure for representing a single row of p2DoneExp table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+    char            *filter;
+    psF32           airmass;
+    psF64           ra;
+    psF64           decl;
+    psF32           exp_time;
+    psF64           background;
+    char            *recipe;
+    psS32           p1_version;
+    psS32           p2_version;
+} p2DoneExpRow;
+
+/** Creates a new p2DoneExpRow object
+ *
+ *  @return A new p2DoneExpRow object or NULL on failure.
+ */
+
+p2DoneExpRow *p2DoneExpRowAlloc(
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles,
+    const char      *filter,
+    psF32           airmass,
+    psF64           ra,
+    psF64           decl,
+    psF32           exp_time,
+    psF64           background,
+    const char      *recipe,
+    psS32           p1_version,
+    psS32           p2_version
+);
+
+/** Creates a new p2DoneExp table
+ *
+ * @return true on success
+ */
+
+bool p2DoneExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a p2DoneExp table
+ *
+ * @return true on success
+ */
+
+bool p2DoneExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool p2DoneExpInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles,
+    const char      *filter,
+    psF32           airmass,
+    psF64           ra,
+    psF64           decl,
+    psF32           exp_time,
+    psF64           background,
+    const char      *recipe,
+    psS32           p1_version,
+    psS32           p2_version
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long p2DoneExpDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool p2DoneExpPop(
+    psDB            *dbh,               ///< Database handle
+    char            **exp_id,
+    char            **camera,
+    char            **telescope,
+    char            **exp_type,
+    psS32           *imfiles,
+    char            **filter,
+    psF32           *airmass,
+    psF64           *ra,
+    psF64           *decl,
+    psF32           *exp_time,
+    psF64           *background,
+    char            **recipe,
+    psS32           *p1_version,
+    psS32           *p2_version
+);
+
+/** Insert a single p2DoneExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool p2DoneExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    p2DoneExpRow    *object             ///< p2DoneExpRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new p2DoneExpRow on success or NULL on failure.
+ */
+
+p2DoneExpRow *p2DoneExpPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table p2DoneExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool p2DoneExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool p2DoneExpPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool p2DoneExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a p2DoneExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *p2DoneExpMetadataFromObject(
+    const p2DoneExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A p2DoneExpRow pointer or NULL on error
+ */
+
+p2DoneExpRow *p2DoneExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as p2DoneExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *p2DoneExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long p2DoneExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** p2DoneImfileRow data structure
+ *
+ * Structure for representing a single row of p2DoneImfile table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *class_id;
+    char            *uri;
+    char            *recipe;
+    psS32           p1_version;
+    psS32           p2_version;
+} p2DoneImfileRow;
+
+/** Creates a new p2DoneImfileRow object
+ *
+ *  @return A new p2DoneImfileRow object or NULL on failure.
+ */
+
+p2DoneImfileRow *p2DoneImfileRowAlloc(
+    const char      *exp_id,
+    const char      *class_id,
+    const char      *uri,
+    const char      *recipe,
+    psS32           p1_version,
+    psS32           p2_version
+);
+
+/** Creates a new p2DoneImfile table
+ *
+ * @return true on success
+ */
+
+bool p2DoneImfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a p2DoneImfile table
+ *
+ * @return true on success
+ */
+
+bool p2DoneImfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool p2DoneImfileInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *class_id,
+    const char      *uri,
+    const char      *recipe,
+    psS32           p1_version,
+    psS32           p2_version
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long p2DoneImfileDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool p2DoneImfilePop(
+    psDB            *dbh,               ///< Database handle
+    char            **exp_id,
+    char            **class_id,
+    char            **uri,
+    char            **recipe,
+    psS32           *p1_version,
+    psS32           *p2_version
+);
+
+/** Insert a single p2DoneImfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool p2DoneImfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    p2DoneImfileRow *object             ///< p2DoneImfileRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new p2DoneImfileRow on success or NULL on failure.
+ */
+
+p2DoneImfileRow *p2DoneImfilePopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table p2DoneImfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool p2DoneImfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool p2DoneImfilePopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool p2DoneImfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a p2DoneImfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *p2DoneImfileMetadataFromObject(
+    const p2DoneImfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A p2DoneImfileRow pointer or NULL on error
+ */
+
+p2DoneImfileRow *p2DoneImfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as p2DoneImfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *p2DoneImfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long p2DoneImfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** p3PendingExpRow data structure
+ *
+ * Structure for representing a single row of p3PendingExp table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *camera;
+    char            *exp_type;
+    psS32           imfiles;
+    char            *filter;
+    psF32           airmass;
+    psF64           ra;
+    psF64           decl;
+    psF32           exp_time;
+    psF64           background;
+    char            *recipe;
+    psS32           p2_version;
+    psS32           p3_version;
+} p3PendingExpRow;
+
+/** Creates a new p3PendingExpRow object
+ *
+ *  @return A new p3PendingExpRow object or NULL on failure.
+ */
+
+p3PendingExpRow *p3PendingExpRowAlloc(
+    const char      *exp_id,
+    const char      *camera,
+    const char      *exp_type,
+    psS32           imfiles,
+    const char      *filter,
+    psF32           airmass,
+    psF64           ra,
+    psF64           decl,
+    psF32           exp_time,
+    psF64           background,
+    const char      *recipe,
+    psS32           p2_version,
+    psS32           p3_version
+);
+
+/** Creates a new p3PendingExp table
+ *
+ * @return true on success
+ */
+
+bool p3PendingExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a p3PendingExp table
+ *
+ * @return true on success
+ */
+
+bool p3PendingExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool p3PendingExpInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *camera,
+    const char      *exp_type,
+    psS32           imfiles,
+    const char      *filter,
+    psF32           airmass,
+    psF64           ra,
+    psF64           decl,
+    psF32           exp_time,
+    psF64           background,
+    const char      *recipe,
+    psS32           p2_version,
+    psS32           p3_version
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long p3PendingExpDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool p3PendingExpPop(
+    psDB            *dbh,               ///< Database handle
+    char            **exp_id,
+    char            **camera,
+    char            **exp_type,
+    psS32           *imfiles,
+    char            **filter,
+    psF32           *airmass,
+    psF64           *ra,
+    psF64           *decl,
+    psF32           *exp_time,
+    psF64           *background,
+    char            **recipe,
+    psS32           *p2_version,
+    psS32           *p3_version
+);
+
+/** Insert a single p3PendingExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool p3PendingExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    p3PendingExpRow *object             ///< p3PendingExpRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new p3PendingExpRow on success or NULL on failure.
+ */
+
+p3PendingExpRow *p3PendingExpPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table p3PendingExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool p3PendingExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool p3PendingExpPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool p3PendingExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a p3PendingExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *p3PendingExpMetadataFromObject(
+    const p3PendingExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A p3PendingExpRow pointer or NULL on error
+ */
+
+p3PendingExpRow *p3PendingExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as p3PendingExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *p3PendingExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long p3PendingExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** detRunRow data structure
+ *
+ * Structure for representing a single row of detRun table data.
+ */
+
+typedef struct {
+    psS32           iteration;
+    char            *det_type;
+} detRunRow;
+
+/** Creates a new detRunRow object
+ *
+ *  @return A new detRunRow object or NULL on failure.
+ */
+
+detRunRow *detRunRowAlloc(
+    psS32           iteration,
+    const char      *det_type
+);
+
+/** Creates a new detRun table
+ *
+ * @return true on success
+ */
+
+bool detRunCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a detRun table
+ *
+ * @return true on success
+ */
+
+bool detRunDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detRunInsert(
+    psDB            *dbh,               ///< Database handle
+    psS32           iteration,
+    const char      *det_type
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detRunDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool detRunPop(
+    psDB            *dbh,               ///< Database handle
+    psS32           *iteration,
+    char            **det_type
+);
+
+/** Insert a single detRunRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detRunInsertObject(
+    psDB            *dbh,               ///< Database handle
+    detRunRow       *object             ///< detRunRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new detRunRow on success or NULL on failure.
+ */
+
+detRunRow *detRunPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table detRunRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool detRunInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool detRunPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool detRunSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a detRunRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *detRunMetadataFromObject(
+    const detRunRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A detRunRow pointer or NULL on error
+ */
+
+detRunRow *detRunObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as detRunRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *detRunSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detRunDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** detInputExpRow data structure
+ *
+ * Structure for representing a single row of detInputExp table data.
+ */
+
+typedef struct {
+    psS32           det_id;
+    psS32           iteration;
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    psS32           imfiles;
+    char            *filter;
+    psF32           airmass;
+    psF64           ra;
+    psF64           decl;
+    psF32           exp_time;
+    psF64           background;
+} detInputExpRow;
+
+/** Creates a new detInputExpRow object
+ *
+ *  @return A new detInputExpRow object or NULL on failure.
+ */
+
+detInputExpRow *detInputExpRowAlloc(
+    psS32           det_id,
+    psS32           iteration,
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles,
+    const char      *filter,
+    psF32           airmass,
+    psF64           ra,
+    psF64           decl,
+    psF32           exp_time,
+    psF64           background
+);
+
+/** Creates a new detInputExp table
+ *
+ * @return true on success
+ */
+
+bool detInputExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a detInputExp table
+ *
+ * @return true on success
+ */
+
+bool detInputExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detInputExpInsert(
+    psDB            *dbh,               ///< Database handle
+    psS32           det_id,
+    psS32           iteration,
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    psS32           imfiles,
+    const char      *filter,
+    psF32           airmass,
+    psF64           ra,
+    psF64           decl,
+    psF32           exp_time,
+    psF64           background
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detInputExpDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool detInputExpPop(
+    psDB            *dbh,               ///< Database handle
+    psS32           *det_id,
+    psS32           *iteration,
+    char            **exp_id,
+    char            **camera,
+    char            **telescope,
+    char            **exp_type,
+    psS32           *imfiles,
+    char            **filter,
+    psF32           *airmass,
+    psF64           *ra,
+    psF64           *decl,
+    psF32           *exp_time,
+    psF64           *background
+);
+
+/** Insert a single detInputExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detInputExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    detInputExpRow  *object             ///< detInputExpRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new detInputExpRow on success or NULL on failure.
+ */
+
+detInputExpRow *detInputExpPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table detInputExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool detInputExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool detInputExpPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool detInputExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a detInputExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *detInputExpMetadataFromObject(
+    const detInputExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A detInputExpRow pointer or NULL on error
+ */
+
+detInputExpRow *detInputExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as detInputExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *detInputExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detInputExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** detProcessedImfileRow data structure
+ *
+ * Structure for representing a single row of detProcessedImfile table data.
+ */
+
+typedef struct {
+    psS32           det_id;
+    char            *exp_id;
+    char            *class_id;
+    char            *uri;
+    char            *recipe;
+} detProcessedImfileRow;
+
+/** Creates a new detProcessedImfileRow object
+ *
+ *  @return A new detProcessedImfileRow object or NULL on failure.
+ */
+
+detProcessedImfileRow *detProcessedImfileRowAlloc(
+    psS32           det_id,
+    const char      *exp_id,
+    const char      *class_id,
+    const char      *uri,
+    const char      *recipe
+);
+
+/** Creates a new detProcessedImfile table
+ *
+ * @return true on success
+ */
+
+bool detProcessedImfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a detProcessedImfile table
+ *
+ * @return true on success
+ */
+
+bool detProcessedImfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detProcessedImfileInsert(
+    psDB            *dbh,               ///< Database handle
+    psS32           det_id,
+    const char      *exp_id,
+    const char      *class_id,
+    const char      *uri,
+    const char      *recipe
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detProcessedImfileDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool detProcessedImfilePop(
+    psDB            *dbh,               ///< Database handle
+    psS32           *det_id,
+    char            **exp_id,
+    char            **class_id,
+    char            **uri,
+    char            **recipe
+);
+
+/** Insert a single detProcessedImfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detProcessedImfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    detProcessedImfileRow *object             ///< detProcessedImfileRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new detProcessedImfileRow on success or NULL on failure.
+ */
+
+detProcessedImfileRow *detProcessedImfilePopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table detProcessedImfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool detProcessedImfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool detProcessedImfilePopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool detProcessedImfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a detProcessedImfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *detProcessedImfileMetadataFromObject(
+    const detProcessedImfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A detProcessedImfileRow pointer or NULL on error
+ */
+
+detProcessedImfileRow *detProcessedImfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as detProcessedImfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *detProcessedImfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detProcessedImfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** detStackedImfileRow data structure
+ *
+ * Structure for representing a single row of detStackedImfile table data.
+ */
+
+typedef struct {
+    psS32           det_id;
+    psS32           iteration;
+    char            *class_id;
+    char            *uri;
+    char            *recipe;
+} detStackedImfileRow;
+
+/** Creates a new detStackedImfileRow object
+ *
+ *  @return A new detStackedImfileRow object or NULL on failure.
+ */
+
+detStackedImfileRow *detStackedImfileRowAlloc(
+    psS32           det_id,
+    psS32           iteration,
+    const char      *class_id,
+    const char      *uri,
+    const char      *recipe
+);
+
+/** Creates a new detStackedImfile table
+ *
+ * @return true on success
+ */
+
+bool detStackedImfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a detStackedImfile table
+ *
+ * @return true on success
+ */
+
+bool detStackedImfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detStackedImfileInsert(
+    psDB            *dbh,               ///< Database handle
+    psS32           det_id,
+    psS32           iteration,
+    const char      *class_id,
+    const char      *uri,
+    const char      *recipe
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detStackedImfileDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool detStackedImfilePop(
+    psDB            *dbh,               ///< Database handle
+    psS32           *det_id,
+    psS32           *iteration,
+    char            **class_id,
+    char            **uri,
+    char            **recipe
+);
+
+/** Insert a single detStackedImfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detStackedImfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    detStackedImfileRow *object             ///< detStackedImfileRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new detStackedImfileRow on success or NULL on failure.
+ */
+
+detStackedImfileRow *detStackedImfilePopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table detStackedImfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool detStackedImfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool detStackedImfilePopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool detStackedImfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a detStackedImfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *detStackedImfileMetadataFromObject(
+    const detStackedImfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A detStackedImfileRow pointer or NULL on error
+ */
+
+detStackedImfileRow *detStackedImfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as detStackedImfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *detStackedImfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detStackedImfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** detNormalizedImfileRow data structure
+ *
+ * Structure for representing a single row of detNormalizedImfile table data.
+ */
+
+typedef struct {
+    psS32           det_id;
+    psS32           iteration;
+    char            *class_id;
+    char            *uri;
+    char            *recipe;
+} detNormalizedImfileRow;
+
+/** Creates a new detNormalizedImfileRow object
+ *
+ *  @return A new detNormalizedImfileRow object or NULL on failure.
+ */
+
+detNormalizedImfileRow *detNormalizedImfileRowAlloc(
+    psS32           det_id,
+    psS32           iteration,
+    const char      *class_id,
+    const char      *uri,
+    const char      *recipe
+);
+
+/** Creates a new detNormalizedImfile table
+ *
+ * @return true on success
+ */
+
+bool detNormalizedImfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a detNormalizedImfile table
+ *
+ * @return true on success
+ */
+
+bool detNormalizedImfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detNormalizedImfileInsert(
+    psDB            *dbh,               ///< Database handle
+    psS32           det_id,
+    psS32           iteration,
+    const char      *class_id,
+    const char      *uri,
+    const char      *recipe
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detNormalizedImfileDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool detNormalizedImfilePop(
+    psDB            *dbh,               ///< Database handle
+    psS32           *det_id,
+    psS32           *iteration,
+    char            **class_id,
+    char            **uri,
+    char            **recipe
+);
+
+/** Insert a single detNormalizedImfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detNormalizedImfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    detNormalizedImfileRow *object             ///< detNormalizedImfileRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new detNormalizedImfileRow on success or NULL on failure.
+ */
+
+detNormalizedImfileRow *detNormalizedImfilePopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table detNormalizedImfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool detNormalizedImfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool detNormalizedImfilePopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool detNormalizedImfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a detNormalizedImfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *detNormalizedImfileMetadataFromObject(
+    const detNormalizedImfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A detNormalizedImfileRow pointer or NULL on error
+ */
+
+detNormalizedImfileRow *detNormalizedImfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as detNormalizedImfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *detNormalizedImfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detNormalizedImfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** detMasterFrameRow data structure
+ *
+ * Structure for representing a single row of detMasterFrame table data.
+ */
+
+typedef struct {
+    psS32           det_id;
+    psS32           iteration;
+    char            *comment;
+} detMasterFrameRow;
+
+/** Creates a new detMasterFrameRow object
+ *
+ *  @return A new detMasterFrameRow object or NULL on failure.
+ */
+
+detMasterFrameRow *detMasterFrameRowAlloc(
+    psS32           det_id,
+    psS32           iteration,
+    const char      *comment
+);
+
+/** Creates a new detMasterFrame table
+ *
+ * @return true on success
+ */
+
+bool detMasterFrameCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a detMasterFrame table
+ *
+ * @return true on success
+ */
+
+bool detMasterFrameDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detMasterFrameInsert(
+    psDB            *dbh,               ///< Database handle
+    psS32           det_id,
+    psS32           iteration,
+    const char      *comment
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detMasterFrameDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool detMasterFramePop(
+    psDB            *dbh,               ///< Database handle
+    psS32           *det_id,
+    psS32           *iteration,
+    char            **comment
+);
+
+/** Insert a single detMasterFrameRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detMasterFrameInsertObject(
+    psDB            *dbh,               ///< Database handle
+    detMasterFrameRow *object             ///< detMasterFrameRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new detMasterFrameRow on success or NULL on failure.
+ */
+
+detMasterFrameRow *detMasterFramePopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table detMasterFrameRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool detMasterFrameInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool detMasterFramePopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool detMasterFrameSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a detMasterFrameRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *detMasterFrameMetadataFromObject(
+    const detMasterFrameRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A detMasterFrameRow pointer or NULL on error
+ */
+
+detMasterFrameRow *detMasterFrameObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as detMasterFrameRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *detMasterFrameSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detMasterFrameDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** detMasterImfileRow data structure
+ *
+ * Structure for representing a single row of detMasterImfile table data.
+ */
+
+typedef struct {
+    psS32           det_id;
+    char            *class_id;
+    char            *uri;
+    char            *recipe;
+} detMasterImfileRow;
+
+/** Creates a new detMasterImfileRow object
+ *
+ *  @return A new detMasterImfileRow object or NULL on failure.
+ */
+
+detMasterImfileRow *detMasterImfileRowAlloc(
+    psS32           det_id,
+    const char      *class_id,
+    const char      *uri,
+    const char      *recipe
+);
+
+/** Creates a new detMasterImfile table
+ *
+ * @return true on success
+ */
+
+bool detMasterImfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a detMasterImfile table
+ *
+ * @return true on success
+ */
+
+bool detMasterImfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detMasterImfileInsert(
+    psDB            *dbh,               ///< Database handle
+    psS32           det_id,
+    const char      *class_id,
+    const char      *uri,
+    const char      *recipe
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detMasterImfileDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool detMasterImfilePop(
+    psDB            *dbh,               ///< Database handle
+    psS32           *det_id,
+    char            **class_id,
+    char            **uri,
+    char            **recipe
+);
+
+/** Insert a single detMasterImfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detMasterImfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    detMasterImfileRow *object             ///< detMasterImfileRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new detMasterImfileRow on success or NULL on failure.
+ */
+
+detMasterImfileRow *detMasterImfilePopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table detMasterImfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool detMasterImfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool detMasterImfilePopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool detMasterImfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a detMasterImfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *detMasterImfileMetadataFromObject(
+    const detMasterImfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A detMasterImfileRow pointer or NULL on error
+ */
+
+detMasterImfileRow *detMasterImfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as detMasterImfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *detMasterImfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detMasterImfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** detResidImfileAnalysisRow data structure
+ *
+ * Structure for representing a single row of detResidImfileAnalysis table data.
+ */
+
+typedef struct {
+    psS32           det_id;
+    psS32           iteration;
+    char            *exp_id;
+    char            *class_id;
+    char            *recipe;
+    char            *uri;
+    char            *b1_uri;
+    char            *b2_uri;
+} detResidImfileAnalysisRow;
+
+/** Creates a new detResidImfileAnalysisRow object
+ *
+ *  @return A new detResidImfileAnalysisRow object or NULL on failure.
+ */
+
+detResidImfileAnalysisRow *detResidImfileAnalysisRowAlloc(
+    psS32           det_id,
+    psS32           iteration,
+    const char      *exp_id,
+    const char      *class_id,
+    const char      *recipe,
+    const char      *uri,
+    const char      *b1_uri,
+    const char      *b2_uri
+);
+
+/** Creates a new detResidImfileAnalysis table
+ *
+ * @return true on success
+ */
+
+bool detResidImfileAnalysisCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a detResidImfileAnalysis table
+ *
+ * @return true on success
+ */
+
+bool detResidImfileAnalysisDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detResidImfileAnalysisInsert(
+    psDB            *dbh,               ///< Database handle
+    psS32           det_id,
+    psS32           iteration,
+    const char      *exp_id,
+    const char      *class_id,
+    const char      *recipe,
+    const char      *uri,
+    const char      *b1_uri,
+    const char      *b2_uri
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detResidImfileAnalysisDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool detResidImfileAnalysisPop(
+    psDB            *dbh,               ///< Database handle
+    psS32           *det_id,
+    psS32           *iteration,
+    char            **exp_id,
+    char            **class_id,
+    char            **recipe,
+    char            **uri,
+    char            **b1_uri,
+    char            **b2_uri
+);
+
+/** Insert a single detResidImfileAnalysisRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detResidImfileAnalysisInsertObject(
+    psDB            *dbh,               ///< Database handle
+    detResidImfileAnalysisRow *object             ///< detResidImfileAnalysisRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new detResidImfileAnalysisRow on success or NULL on failure.
+ */
+
+detResidImfileAnalysisRow *detResidImfileAnalysisPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table detResidImfileAnalysisRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool detResidImfileAnalysisInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool detResidImfileAnalysisPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool detResidImfileAnalysisSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a detResidImfileAnalysisRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *detResidImfileAnalysisMetadataFromObject(
+    const detResidImfileAnalysisRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A detResidImfileAnalysisRow pointer or NULL on error
+ */
+
+detResidImfileAnalysisRow *detResidImfileAnalysisObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as detResidImfileAnalysisRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *detResidImfileAnalysisSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detResidImfileAnalysisDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** detResidExpAnalysisRow data structure
+ *
+ * Structure for representing a single row of detResidExpAnalysis table data.
+ */
+
+typedef struct {
+    psS32           det_id;
+    psS32           iteration;
+    char            *exp_id;
+    char            *recipe;
+    bool            accept;
+} detResidExpAnalysisRow;
+
+/** Creates a new detResidExpAnalysisRow object
+ *
+ *  @return A new detResidExpAnalysisRow object or NULL on failure.
+ */
+
+detResidExpAnalysisRow *detResidExpAnalysisRowAlloc(
+    psS32           det_id,
+    psS32           iteration,
+    const char      *exp_id,
+    const char      *recipe,
+    bool            accept
+);
+
+/** Creates a new detResidExpAnalysis table
+ *
+ * @return true on success
+ */
+
+bool detResidExpAnalysisCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a detResidExpAnalysis table
+ *
+ * @return true on success
+ */
+
+bool detResidExpAnalysisDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detResidExpAnalysisInsert(
+    psDB            *dbh,               ///< Database handle
+    psS32           det_id,
+    psS32           iteration,
+    const char      *exp_id,
+    const char      *recipe,
+    bool            accept
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detResidExpAnalysisDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool detResidExpAnalysisPop(
+    psDB            *dbh,               ///< Database handle
+    psS32           *det_id,
+    psS32           *iteration,
+    char            **exp_id,
+    char            **recipe,
+    bool            *accept
+);
+
+/** Insert a single detResidExpAnalysisRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detResidExpAnalysisInsertObject(
+    psDB            *dbh,               ///< Database handle
+    detResidExpAnalysisRow *object             ///< detResidExpAnalysisRow object
+);
+
+/** Removes the last row from the database and returns it
+ *
+ * @return A new detResidExpAnalysisRow on success or NULL on failure.
+ */
+
+detResidExpAnalysisRow *detResidExpAnalysisPopObject(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert data from a binary FITS table detResidExpAnalysisRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool detResidExpAnalysisInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool detResidExpAnalysisPopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool detResidExpAnalysisSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a detResidExpAnalysisRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *detResidExpAnalysisMetadataFromObject(
+    const detResidExpAnalysisRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A detResidExpAnalysisRow pointer or NULL on error
+ */
+
+detResidExpAnalysisRow *detResidExpAnalysisObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as detResidExpAnalysisRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *detResidExpAnalysisSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detResidExpAnalysisDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/// @}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // DETRESIDEXPANALYSIS_DB_H
Index: /branches/jh_branches/jhoblitt/ippdb/tests/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/ippdb/tests/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/ippdb/tests/Makefile.am	(revision 22254)
@@ -0,0 +1,67 @@
+# copied from bison-1.875d
+
+EXTRA_DIST = $(TESTSUITE_AT) testsuite package.m4
+
+DISTCLEANFILES       = atconfig $(check_SCRIPTS)
+MAINTAINERCLEANFILES = Makefile.in $(TESTSUITE)
+
+## ------------ ##
+## package.m4.  ##
+## ------------ ##
+
+$(srcdir)/package.m4: $(top_srcdir)/configure.ac
+	{					\
+	  echo '# Signature of the current package.'; \
+	  echo 'm4_define([AT_PACKAGE_NAME],      [@PACKAGE_NAME@])'; \
+	  echo 'm4_define([AT_PACKAGE_TARNAME],   [@PACKAGE_TARNAME@])'; \
+	  echo 'm4_define([AT_PACKAGE_VERSION],   [@PACKAGE_VERSION@])'; \
+	  echo 'm4_define([AT_PACKAGE_STRING],    [@PACKAGE_STRING@])'; \
+	  echo 'm4_define([AT_PACKAGE_BUGREPORT], [@PACKAGE_BUGREPORT@])'; \
+	} >$(srcdir)/package.m4
+
+## ------------ ##
+## Test suite.  ##
+## ------------ ##
+
+TESTSUITE = $(srcdir)/testsuite
+
+AUTOTEST = $(AUTOM4TE) --language=autotest
+$(TESTSUITE): package.m4 $(TESTSUITE_AT)
+	$(AUTOTEST) -I $(srcdir) testsuite.at -o $@.tmp
+	mv $@.tmp $@
+
+atconfig: $(top_builddir)/config.status
+	cd $(top_builddir) && ./config.status tests/$@
+
+clean-local:
+	test ! -f $(TESTSUITE) || $(SHELL) $(TESTSUITE) --clean
+
+check-local: atconfig $(TESTSUITE)
+	$(SHELL) $(TESTSUITE)
+
+#
+# END BISON
+#
+
+UNITS = \
+	alloc \
+	init \
+	cleanup \
+	createtable \
+    droptable \
+    insert \
+    pop \
+    insertobject \
+    popobject \
+    insertfits \
+    popfits \
+    selectrowsfits \
+    metadatafromobject \
+    objectfrommetadata
+
+AM_CPPFLAGS = -I$(top_srcdir)/src$
+AM_CFLAGS   = @ippdb_CFLAGS@ $(PSLIB_CFLAGS)$
+AM_LDFLAGS  = $(PSLIB_LIBS)$
+LDADD       = $(top_builddir)/src/libippdb.la$
+
+check_PROGRAMS = dbsetup dbcleanup $(UNITS)
Index: /branches/jh_branches/jhoblitt/ippdb/tests/alloc.c
===================================================================
--- /branches/jh_branches/jhoblitt/ippdb/tests/alloc.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/ippdb/tests/alloc.c	(revision 22254)
@@ -0,0 +1,1285 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+
+#define MAX_STRING_LENGTH 1024
+
+int main ()
+{
+    {
+        weatherRow      *object;
+
+        object = weatherRowAlloc(32.32, 32.32, 32.32, 32.32, 32.32, 32.32, 32.32    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->temp01 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->humi01 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->temp02 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->humi02 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->temp03 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->humi03 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->pressure == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        skyp_transparencyRow *object;
+
+        object = skyp_transparencyRowAlloc("a string", 64.64, -32, 64.64, 64.64, 32.32, 64.64    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->trans == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->nstars == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exptime == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->sky_bright == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        skyp_absorptionRow *object;
+
+        object = skyp_absorptionRowAlloc("a string", 32.32, 32.32, 32.32, -32, 64.64, 64.64, 32.32, 64.64    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->disperser_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->atmcomp1 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->atmcomp2 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->atmcomp3 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->nstars == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exptime == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->sky_bright == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        skyp_emissionRow *object;
+
+        object = skyp_emissionRowAlloc("a string", 32.32, 32.32, 32.32, 32.32, 32.32    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->disperser_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->atmcomp1 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->atmcomp2 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->atmcomp3 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->continuum == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exptime == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        dimmRow         *object;
+
+        object = dimmRowAlloc(32.32, 32.32, 32.32, 64.64, 64.64, 32.32, "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->sigmax == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->sigmay == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->fwhm == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->expttime == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        skyp_irRow      *object;
+
+        object = skyp_irRowAlloc(64.64, 64.64, 64.64, 64.64, 32.32, 32.32    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->sky_bright == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->sky_var == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->fov_x == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->fov_y == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        domeRow         *object;
+
+        object = domeRowAlloc(32.32, true, true, true    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->az == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->open == true) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->light == true) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->track == true) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        telescopeRow    *object;
+
+        object = telescopeRowAlloc("a string", 32.32, 32.32, 64.64, 64.64    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->guide, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->alt == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->az == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        summitExpRow    *object;
+
+        object = summitExpRowAlloc("a string", "a string", "a string", "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        pzPendingExpRow *object;
+
+        object = pzPendingExpRowAlloc("a string", "a string", "a string", "a string", -32    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        pzPendingImfileRow *object;
+
+        object = pzPendingImfileRowAlloc("a string", -32, "a string", "a string", "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bytes == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->md5sum, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        newExpRow       *object;
+
+        object = newExpRowAlloc("a string", "a string", "a string", "a string", -32    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        newImfileRow    *object;
+
+        object = newImfileRowAlloc("a string", "a string", "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        rawDetrendExpRow *object;
+
+        object = rawDetrendExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string", 32.32, 64.64, 64.64, 32.32, 64.64    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->airmass == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exp_time == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->background == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        rawScienceExpRow *object;
+
+        object = rawScienceExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string", 32.32, 64.64, 64.64, 32.32, 64.64    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->airmass == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exp_time == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->background == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        rawImfileRow    *object;
+
+        object = rawImfileRowAlloc("a string", "a string", "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        p1PendingExpRow *object;
+
+        object = p1PendingExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string", 32.32, 64.64, 64.64, 32.32, 64.64, "a string", -32    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->airmass == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exp_time == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->background == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p1_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        p2PendingExpRow *object;
+
+        object = p2PendingExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string", 32.32, 32.32, 64.64, 64.64, 64.64, "a string", -32, -32    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->airmass == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exp_time == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->background == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p1_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p2_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        p2PendingImfileRow *object;
+
+        object = p2PendingImfileRowAlloc("a string", "a string", "a string", "a string", -32, -32    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p1_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p2_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        p2DoneExpRow    *object;
+
+        object = p2DoneExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string", 32.32, 64.64, 64.64, 32.32, 64.64, "a string", -32, -32    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->airmass == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exp_time == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->background == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p1_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p2_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        p2DoneImfileRow *object;
+
+        object = p2DoneImfileRowAlloc("a string", "a string", "a string", "a string", -32, -32    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p1_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p2_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        p3PendingExpRow *object;
+
+        object = p3PendingExpRowAlloc("a string", "a string", "a string", -32, "a string", 32.32, 64.64, 64.64, 32.32, 64.64, "a string", -32, -32    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->airmass == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exp_time == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->background == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p2_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p3_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        detRunRow       *object;
+
+        object = detRunRowAlloc(-32, "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->det_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        detInputExpRow  *object;
+
+        object = detInputExpRowAlloc(-32, -32, "a string", "a string", "a string", "a string", -32, "a string", 32.32, 64.64, 64.64, 32.32, 64.64    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->airmass == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exp_time == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->background == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        detProcessedImfileRow *object;
+
+        object = detProcessedImfileRowAlloc(-32, "a string", "a string", "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        detStackedImfileRow *object;
+
+        object = detStackedImfileRowAlloc(-32, -32, "a string", "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        detNormalizedImfileRow *object;
+
+        object = detNormalizedImfileRowAlloc(-32, -32, "a string", "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        detMasterFrameRow *object;
+
+        object = detMasterFrameRowAlloc(-32, -32, "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->comment, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        detMasterImfileRow *object;
+
+        object = detMasterImfileRowAlloc(-32, "a string", "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        detResidImfileAnalysisRow *object;
+
+        object = detResidImfileAnalysisRowAlloc(-32, -32, "a string", "a string", "a string", "a string", "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->b1_uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->b2_uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        detResidExpAnalysisRow *object;
+
+        object = detResidExpAnalysisRowAlloc(-32, -32, "a string", "a string", true    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->accept == true) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /branches/jh_branches/jhoblitt/ippdb/tests/cleanup.c
===================================================================
--- /branches/jh_branches/jhoblitt/ippdb/tests/cleanup.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/ippdb/tests/cleanup.c	(revision 22254)
@@ -0,0 +1,17 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+
+int main ()
+{
+    psDB            *dbh;
+
+    dbh = psDBInit("localhost", "test", NULL, "test");
+    if (!dbh) {
+        exit(EXIT_FAILURE);
+    }
+
+    ippdbCleanup(dbh);
+
+    exit(EXIT_SUCCESS);
+}
Index: /branches/jh_branches/jhoblitt/ippdb/tests/createtable.c
===================================================================
--- /branches/jh_branches/jhoblitt/ippdb/tests/createtable.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/ippdb/tests/createtable.c	(revision 22254)
@@ -0,0 +1,473 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+
+int main ()
+{
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!weatherCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!skyp_transparencyCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!skyp_absorptionCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!skyp_emissionCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!dimmCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!skyp_irCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!domeCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!telescopeCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!summitExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!pzPendingExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!pzPendingImfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!newExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!newImfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!rawDetrendExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!rawScienceExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!rawImfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!p1PendingExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!p2PendingExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!p2PendingImfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!p2DoneExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!p2DoneImfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!p3PendingExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!detRunCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!detInputExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!detProcessedImfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!detStackedImfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!detNormalizedImfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!detMasterFrameCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!detMasterImfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!detResidImfileAnalysisCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!detResidExpAnalysisCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /branches/jh_branches/jhoblitt/ippdb/tests/dbcleanup.c
===================================================================
--- /branches/jh_branches/jhoblitt/ippdb/tests/dbcleanup.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/ippdb/tests/dbcleanup.c	(revision 22254)
@@ -0,0 +1,48 @@
+#include <pslib.h>
+#include <stdlib.h>
+
+int main ()
+{
+    psDB            *dbh;
+
+    dbh = psDBInit("localhost", "test", NULL, "test");
+    if (!dbh) {
+        exit(EXIT_FAILURE);
+    }
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS weather");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS skyp_transparency");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS skyp_absorption");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS skyp_emission");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS dimm");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS skyp_ir");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS dome");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS telescope");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS summitExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS pzPendingExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS pzPendingImfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS newExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS newImfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS rawDetrendExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS rawScienceExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS rawImfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS p1PendingExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS p2PendingExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS p2PendingImfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS p2DoneExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS p2DoneImfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS p3PendingExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detRun");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detInputExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detProcessedImfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detStackedImfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detNormalizedImfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detMasterFrame");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detMasterImfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detResidImfileAnalysis");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detResidExpAnalysis");
+
+    psDBCleanup(dbh);
+
+    exit(EXIT_SUCCESS);
+}
Index: /branches/jh_branches/jhoblitt/ippdb/tests/dbsetup.c
===================================================================
--- /branches/jh_branches/jhoblitt/ippdb/tests/dbsetup.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/ippdb/tests/dbsetup.c	(revision 22254)
@@ -0,0 +1,111 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+
+int main ()
+{
+    psDB            *dbh;
+
+    dbh = psDBInit("localhost", "test", NULL, "test");
+    if (!dbh) {
+        exit(EXIT_FAILURE);
+    }
+
+    // remove the table if it already exists
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS weather");
+    weatherCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS skyp_transparency");
+    skyp_transparencyCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS skyp_absorption");
+    skyp_absorptionCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS skyp_emission");
+    skyp_emissionCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS dimm");
+    dimmCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS skyp_ir");
+    skyp_irCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS dome");
+    domeCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS telescope");
+    telescopeCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS summitExp");
+    summitExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS pzPendingExp");
+    pzPendingExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS pzPendingImfile");
+    pzPendingImfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS newExp");
+    newExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS newImfile");
+    newImfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS rawDetrendExp");
+    rawDetrendExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS rawScienceExp");
+    rawScienceExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS rawImfile");
+    rawImfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS p1PendingExp");
+    p1PendingExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS p2PendingExp");
+    p2PendingExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS p2PendingImfile");
+    p2PendingImfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS p2DoneExp");
+    p2DoneExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS p2DoneImfile");
+    p2DoneImfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS p3PendingExp");
+    p3PendingExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detRun");
+    detRunCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detInputExp");
+    detInputExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detProcessedImfile");
+    detProcessedImfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detStackedImfile");
+    detStackedImfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detNormalizedImfile");
+    detNormalizedImfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detMasterFrame");
+    detMasterFrameCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detMasterImfile");
+    detMasterImfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detResidImfileAnalysis");
+    detResidImfileAnalysisCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detResidExpAnalysis");
+    detResidExpAnalysisCreateTable(dbh);
+
+    psDBCleanup(dbh);
+
+    exit(EXIT_SUCCESS);
+}
Index: /branches/jh_branches/jhoblitt/ippdb/tests/droptable.c
===================================================================
--- /branches/jh_branches/jhoblitt/ippdb/tests/droptable.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/ippdb/tests/droptable.c	(revision 22254)
@@ -0,0 +1,473 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+
+int main ()
+{
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!weatherDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_transparencyDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_absorptionDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_emissionDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!dimmDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_irDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!domeDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!telescopeDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!summitExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingImfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newImfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawDetrendExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawScienceExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawImfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p1PendingExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2PendingExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2PendingImfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2DoneExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2DoneImfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p3PendingExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detRunDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detInputExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detProcessedImfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detStackedImfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detNormalizedImfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detMasterFrameDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detMasterImfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidImfileAnalysisDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidExpAnalysisDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /branches/jh_branches/jhoblitt/ippdb/tests/init.c
===================================================================
--- /branches/jh_branches/jhoblitt/ippdb/tests/init.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/ippdb/tests/init.c	(revision 22254)
@@ -0,0 +1,17 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+
+int main ()
+{
+    psDB            *dbh;
+
+    dbh = ippdbInit("localhost", "test", NULL, "test");
+    if (!dbh) {
+        exit(EXIT_FAILURE);
+    }
+
+    psDBCleanup(dbh);
+
+    exit(EXIT_SUCCESS);
+}
Index: /branches/jh_branches/jhoblitt/ippdb/tests/insert.c
===================================================================
--- /branches/jh_branches/jhoblitt/ippdb/tests/insert.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/ippdb/tests/insert.c	(revision 22254)
@@ -0,0 +1,473 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+
+int main ()
+{
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!weatherInsert(dbh, 32.32, 32.32, 32.32, 32.32, 32.32, 32.32, 32.32)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_transparencyInsert(dbh, "a string", 64.64, -32, 64.64, 64.64, 32.32, 64.64)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_absorptionInsert(dbh, "a string", 32.32, 32.32, 32.32, -32, 64.64, 64.64, 32.32, 64.64)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_emissionInsert(dbh, "a string", 32.32, 32.32, 32.32, 32.32, 32.32)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!dimmInsert(dbh, 32.32, 32.32, 32.32, 64.64, 64.64, 32.32, "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_irInsert(dbh, 64.64, 64.64, 64.64, 64.64, 32.32, 32.32)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!domeInsert(dbh, 32.32, true, true, true)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!telescopeInsert(dbh, "a string", 32.32, 32.32, 64.64, 64.64)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!summitExpInsert(dbh, "a string", "a string", "a string", "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingExpInsert(dbh, "a string", "a string", "a string", "a string", -32)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingImfileInsert(dbh, "a string", -32, "a string", "a string", "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newExpInsert(dbh, "a string", "a string", "a string", "a string", -32)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newImfileInsert(dbh, "a string", "a string", "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawDetrendExpInsert(dbh, "a string", "a string", "a string", "a string", -32, "a string", 32.32, 64.64, 64.64, 32.32, 64.64)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawScienceExpInsert(dbh, "a string", "a string", "a string", "a string", -32, "a string", 32.32, 64.64, 64.64, 32.32, 64.64)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawImfileInsert(dbh, "a string", "a string", "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p1PendingExpInsert(dbh, "a string", "a string", "a string", "a string", -32, "a string", 32.32, 64.64, 64.64, 32.32, 64.64, "a string", -32)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2PendingExpInsert(dbh, "a string", "a string", "a string", "a string", -32, "a string", 32.32, 32.32, 64.64, 64.64, 64.64, "a string", -32, -32)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2PendingImfileInsert(dbh, "a string", "a string", "a string", "a string", -32, -32)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2DoneExpInsert(dbh, "a string", "a string", "a string", "a string", -32, "a string", 32.32, 64.64, 64.64, 32.32, 64.64, "a string", -32, -32)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2DoneImfileInsert(dbh, "a string", "a string", "a string", "a string", -32, -32)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p3PendingExpInsert(dbh, "a string", "a string", "a string", -32, "a string", 32.32, 64.64, 64.64, 32.32, 64.64, "a string", -32, -32)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detRunInsert(dbh, -32, "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detInputExpInsert(dbh, -32, -32, "a string", "a string", "a string", "a string", -32, "a string", 32.32, 64.64, 64.64, 32.32, 64.64)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detProcessedImfileInsert(dbh, -32, "a string", "a string", "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detStackedImfileInsert(dbh, -32, -32, "a string", "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detNormalizedImfileInsert(dbh, -32, -32, "a string", "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detMasterFrameInsert(dbh, -32, -32, "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detMasterImfileInsert(dbh, -32, "a string", "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidImfileAnalysisInsert(dbh, -32, -32, "a string", "a string", "a string", "a string", "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidExpAnalysisInsert(dbh, -32, -32, "a string", "a string", true)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /branches/jh_branches/jhoblitt/ippdb/tests/insertfits.c
===================================================================
--- /branches/jh_branches/jhoblitt/ippdb/tests/insertfits.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/ippdb/tests/insertfits.c	(revision 22254)
@@ -0,0 +1,818 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#define TMP_FILENAME "./blargh"
+
+int main ()
+{
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!weatherInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_transparencyInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_absorptionInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_emissionInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!dimmInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_irInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!domeInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!telescopeInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!summitExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingImfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newImfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawDetrendExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawScienceExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawImfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p1PendingExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2PendingExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2PendingImfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2DoneExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2DoneImfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p3PendingExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detRunInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detInputExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detProcessedImfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detStackedImfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detNormalizedImfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detMasterFrameInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detMasterImfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidImfileAnalysisInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidExpAnalysisInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /branches/jh_branches/jhoblitt/ippdb/tests/insertobject.c
===================================================================
--- /branches/jh_branches/jhoblitt/ippdb/tests/insertobject.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/ippdb/tests/insertobject.c	(revision 22254)
@@ -0,0 +1,690 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+
+int main ()
+{
+    {
+        psDB            *dbh;
+        weatherRow      *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = weatherRowAlloc(32.32, 32.32, 32.32, 32.32, 32.32, 32.32, 32.32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!weatherInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        skyp_transparencyRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = skyp_transparencyRowAlloc("a string", 64.64, -32, 64.64, 64.64, 32.32, 64.64);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_transparencyInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        skyp_absorptionRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = skyp_absorptionRowAlloc("a string", 32.32, 32.32, 32.32, -32, 64.64, 64.64, 32.32, 64.64);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_absorptionInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        skyp_emissionRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = skyp_emissionRowAlloc("a string", 32.32, 32.32, 32.32, 32.32, 32.32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_emissionInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        dimmRow         *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = dimmRowAlloc(32.32, 32.32, 32.32, 64.64, 64.64, 32.32, "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!dimmInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        skyp_irRow      *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = skyp_irRowAlloc(64.64, 64.64, 64.64, 64.64, 32.32, 32.32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_irInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        domeRow         *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = domeRowAlloc(32.32, true, true, true);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!domeInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        telescopeRow    *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = telescopeRowAlloc("a string", 32.32, 32.32, 64.64, 64.64);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!telescopeInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        summitExpRow    *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = summitExpRowAlloc("a string", "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!summitExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        pzPendingExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = pzPendingExpRowAlloc("a string", "a string", "a string", "a string", -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        pzPendingImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = pzPendingImfileRowAlloc("a string", -32, "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingImfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        newExpRow       *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = newExpRowAlloc("a string", "a string", "a string", "a string", -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        newImfileRow    *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = newImfileRowAlloc("a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newImfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        rawDetrendExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = rawDetrendExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string", 32.32, 64.64, 64.64, 32.32, 64.64);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawDetrendExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        rawScienceExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = rawScienceExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string", 32.32, 64.64, 64.64, 32.32, 64.64);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawScienceExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        rawImfileRow    *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = rawImfileRowAlloc("a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawImfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        p1PendingExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = p1PendingExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string", 32.32, 64.64, 64.64, 32.32, 64.64, "a string", -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p1PendingExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        p2PendingExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = p2PendingExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string", 32.32, 32.32, 64.64, 64.64, 64.64, "a string", -32, -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2PendingExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        p2PendingImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = p2PendingImfileRowAlloc("a string", "a string", "a string", "a string", -32, -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2PendingImfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        p2DoneExpRow    *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = p2DoneExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string", 32.32, 64.64, 64.64, 32.32, 64.64, "a string", -32, -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2DoneExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        p2DoneImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = p2DoneImfileRowAlloc("a string", "a string", "a string", "a string", -32, -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2DoneImfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        p3PendingExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = p3PendingExpRowAlloc("a string", "a string", "a string", -32, "a string", 32.32, 64.64, 64.64, 32.32, 64.64, "a string", -32, -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p3PendingExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detRunRow       *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detRunRowAlloc(-32, "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detRunInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detInputExpRow  *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detInputExpRowAlloc(-32, -32, "a string", "a string", "a string", "a string", -32, "a string", 32.32, 64.64, 64.64, 32.32, 64.64);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detInputExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detProcessedImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detProcessedImfileRowAlloc(-32, "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detProcessedImfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detStackedImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detStackedImfileRowAlloc(-32, -32, "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detStackedImfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detNormalizedImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detNormalizedImfileRowAlloc(-32, -32, "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detNormalizedImfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detMasterFrameRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detMasterFrameRowAlloc(-32, -32, "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detMasterFrameInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detMasterImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detMasterImfileRowAlloc(-32, "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detMasterImfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detResidImfileAnalysisRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detResidImfileAnalysisRowAlloc(-32, -32, "a string", "a string", "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidImfileAnalysisInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detResidExpAnalysisRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detResidExpAnalysisRowAlloc(-32, -32, "a string", "a string", true);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidExpAnalysisInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /branches/jh_branches/jhoblitt/ippdb/tests/metadatafromobject.c
===================================================================
--- /branches/jh_branches/jhoblitt/ippdb/tests/metadatafromobject.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/ippdb/tests/metadatafromobject.c	(revision 22254)
@@ -0,0 +1,1534 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define MAX_STRING_LENGTH 1024
+
+int main ()
+{
+    {
+        psMetadata      *md;
+        weatherRow      *object;
+        bool            status;
+
+        object = weatherRowAlloc(32.32, 32.32, 32.32, 32.32, 32.32, 32.32, 32.32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = weatherMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (!psMetadataLookupF32(&status, md, "temp01") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "humi01") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "temp02") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "humi02") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "temp03") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "humi03") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "pressure") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        skyp_transparencyRow *object;
+        bool            status;
+
+        object = skyp_transparencyRowAlloc("a string", 64.64, -32, 64.64, 64.64, 32.32, 64.64);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = skyp_transparencyMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "filter"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "trans") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "nstars") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "ra") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "decl") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "exptime") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "sky_bright") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        skyp_absorptionRow *object;
+        bool            status;
+
+        object = skyp_absorptionRowAlloc("a string", 32.32, 32.32, 32.32, -32, 64.64, 64.64, 32.32, 64.64);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = skyp_absorptionMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "disperser_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "atmcomp1") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "atmcomp2") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "atmcomp3") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "nstars") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "ra") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "decl") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "exptime") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "sky_bright") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        skyp_emissionRow *object;
+        bool            status;
+
+        object = skyp_emissionRowAlloc("a string", 32.32, 32.32, 32.32, 32.32, 32.32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = skyp_emissionMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "disperser_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "atmcomp1") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "atmcomp2") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "atmcomp3") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "continuum") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "exptime") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        dimmRow         *object;
+        bool            status;
+
+        object = dimmRowAlloc(32.32, 32.32, 32.32, 64.64, 64.64, 32.32, "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = dimmMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (!psMetadataLookupF32(&status, md, "sigmax") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "sigmay") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "fwhm") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "ra") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "decl") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "expttime") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "telescope_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        skyp_irRow      *object;
+        bool            status;
+
+        object = skyp_irRowAlloc(64.64, 64.64, 64.64, 64.64, 32.32, 32.32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = skyp_irMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (!psMetadataLookupF64(&status, md, "sky_bright") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "sky_var") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "ra") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "decl") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "fov_x") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "fov_y") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        domeRow         *object;
+        bool            status;
+
+        object = domeRowAlloc(32.32, true, true, true);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = domeMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (!psMetadataLookupF32(&status, md, "az") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupBool(&status, md, "open") == true) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupBool(&status, md, "light") == true) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupBool(&status, md, "track") == true) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        telescopeRow    *object;
+        bool            status;
+
+        object = telescopeRowAlloc("a string", 32.32, 32.32, 64.64, 64.64);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = telescopeMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "guide"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "alt") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "az") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "ra") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "decl") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        summitExpRow    *object;
+        bool            status;
+
+        object = summitExpRowAlloc("a string", "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = summitExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "camera"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "telescope"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_type"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        pzPendingExpRow *object;
+        bool            status;
+
+        object = pzPendingExpRowAlloc("a string", "a string", "a string", "a string", -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = pzPendingExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "camera"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "telescope"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_type"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "imfiles") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        pzPendingImfileRow *object;
+        bool            status;
+
+        object = pzPendingImfileRowAlloc("a string", -32, "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = pzPendingImfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "bytes") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "md5sum"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        newExpRow       *object;
+        bool            status;
+
+        object = newExpRowAlloc("a string", "a string", "a string", "a string", -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = newExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "camera"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "telescope"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_type"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "imfiles") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        newImfileRow    *object;
+        bool            status;
+
+        object = newImfileRowAlloc("a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = newImfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        rawDetrendExpRow *object;
+        bool            status;
+
+        object = rawDetrendExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string", 32.32, 64.64, 64.64, 32.32, 64.64);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = rawDetrendExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "camera"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "telescope"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_type"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "imfiles") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "filter"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "airmass") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "ra") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "decl") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "exp_time") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "background") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        rawScienceExpRow *object;
+        bool            status;
+
+        object = rawScienceExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string", 32.32, 64.64, 64.64, 32.32, 64.64);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = rawScienceExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "camera"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "telescope"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_type"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "imfiles") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "filter"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "airmass") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "ra") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "decl") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "exp_time") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "background") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        rawImfileRow    *object;
+        bool            status;
+
+        object = rawImfileRowAlloc("a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = rawImfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        p1PendingExpRow *object;
+        bool            status;
+
+        object = p1PendingExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string", 32.32, 64.64, 64.64, 32.32, 64.64, "a string", -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = p1PendingExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "camera"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "telescope"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_type"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "imfiles") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "filter"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "airmass") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "ra") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "decl") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "exp_time") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "background") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "p1_version") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        p2PendingExpRow *object;
+        bool            status;
+
+        object = p2PendingExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string", 32.32, 32.32, 64.64, 64.64, 64.64, "a string", -32, -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = p2PendingExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "camera"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "telescope"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_type"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "imfiles") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "filter"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "airmass") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "ra") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "decl") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "exp_time") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "background") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "p1_version") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "p2_version") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        p2PendingImfileRow *object;
+        bool            status;
+
+        object = p2PendingImfileRowAlloc("a string", "a string", "a string", "a string", -32, -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = p2PendingImfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "p1_version") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "p2_version") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        p2DoneExpRow    *object;
+        bool            status;
+
+        object = p2DoneExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string", 32.32, 64.64, 64.64, 32.32, 64.64, "a string", -32, -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = p2DoneExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "camera"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "telescope"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_type"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "imfiles") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "filter"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "airmass") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "ra") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "decl") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "exp_time") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "background") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "p1_version") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "p2_version") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        p2DoneImfileRow *object;
+        bool            status;
+
+        object = p2DoneImfileRowAlloc("a string", "a string", "a string", "a string", -32, -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = p2DoneImfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "p1_version") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "p2_version") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        p3PendingExpRow *object;
+        bool            status;
+
+        object = p3PendingExpRowAlloc("a string", "a string", "a string", -32, "a string", 32.32, 64.64, 64.64, 32.32, 64.64, "a string", -32, -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = p3PendingExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "camera"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_type"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "imfiles") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "filter"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "airmass") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "ra") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "decl") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "exp_time") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "background") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "p2_version") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "p3_version") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        detRunRow       *object;
+        bool            status;
+
+        object = detRunRowAlloc(-32, "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = detRunMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (!psMetadataLookupS32(&status, md, "iteration") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "det_type"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        detInputExpRow  *object;
+        bool            status;
+
+        object = detInputExpRowAlloc(-32, -32, "a string", "a string", "a string", "a string", -32, "a string", 32.32, 64.64, 64.64, 32.32, 64.64);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = detInputExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (!psMetadataLookupS32(&status, md, "det_id") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "iteration") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "camera"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "telescope"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_type"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "imfiles") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "filter"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "airmass") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "ra") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "decl") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "exp_time") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "background") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        detProcessedImfileRow *object;
+        bool            status;
+
+        object = detProcessedImfileRowAlloc(-32, "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = detProcessedImfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (!psMetadataLookupS32(&status, md, "det_id") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        detStackedImfileRow *object;
+        bool            status;
+
+        object = detStackedImfileRowAlloc(-32, -32, "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = detStackedImfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (!psMetadataLookupS32(&status, md, "det_id") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "iteration") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        detNormalizedImfileRow *object;
+        bool            status;
+
+        object = detNormalizedImfileRowAlloc(-32, -32, "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = detNormalizedImfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (!psMetadataLookupS32(&status, md, "det_id") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "iteration") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        detMasterFrameRow *object;
+        bool            status;
+
+        object = detMasterFrameRowAlloc(-32, -32, "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = detMasterFrameMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (!psMetadataLookupS32(&status, md, "det_id") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "iteration") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "comment"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        detMasterImfileRow *object;
+        bool            status;
+
+        object = detMasterImfileRowAlloc(-32, "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = detMasterImfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (!psMetadataLookupS32(&status, md, "det_id") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        detResidImfileAnalysisRow *object;
+        bool            status;
+
+        object = detResidImfileAnalysisRowAlloc(-32, -32, "a string", "a string", "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = detResidImfileAnalysisMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (!psMetadataLookupS32(&status, md, "det_id") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "iteration") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "b1_uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "b2_uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        detResidExpAnalysisRow *object;
+        bool            status;
+
+        object = detResidExpAnalysisRowAlloc(-32, -32, "a string", "a string", true);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = detResidExpAnalysisMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (!psMetadataLookupS32(&status, md, "det_id") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "iteration") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupBool(&status, md, "accept") == true) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /branches/jh_branches/jhoblitt/ippdb/tests/objectfrommetadata.c
===================================================================
--- /branches/jh_branches/jhoblitt/ippdb/tests/objectfrommetadata.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/ippdb/tests/objectfrommetadata.c	(revision 22254)
@@ -0,0 +1,2313 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define MAX_STRING_LENGTH 1024
+
+int main ()
+{
+    {
+        psMetadata      *md;
+        weatherRow      *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "temp01", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "humi01", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "temp02", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "humi02", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "temp03", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "humi03", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "pressure", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = weatherObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (!object->temp01 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->humi01 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->temp02 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->humi02 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->temp03 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->humi03 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->pressure == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        skyp_transparencyRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "trans", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "nstars", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "exptime", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_bright", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = skyp_transparencyObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->trans == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->nstars == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exptime == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->sky_bright == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        skyp_absorptionRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "disperser_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp1", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp2", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp3", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "nstars", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "exptime", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_bright", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = skyp_absorptionObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->disperser_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->atmcomp1 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->atmcomp2 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->atmcomp3 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->nstars == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exptime == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->sky_bright == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        skyp_emissionRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "disperser_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp1", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp2", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "atmcomp3", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "continuum", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "exptime", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = skyp_emissionObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->disperser_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->atmcomp1 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->atmcomp2 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->atmcomp3 == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->continuum == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exptime == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        dimmRow         *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "sigmax", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "sigmay", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "fwhm", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "expttime", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = dimmObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (!object->sigmax == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->sigmay == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->fwhm == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->expttime == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        skyp_irRow      *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_bright", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "sky_var", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "fov_x", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "fov_y", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = skyp_irObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (!object->sky_bright == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->sky_var == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->fov_x == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->fov_y == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        domeRow         *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "az", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAdd(md, PS_LIST_TAIL, "open", PS_DATA_BOOL, NULL, true)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAdd(md, PS_LIST_TAIL, "light", PS_DATA_BOOL, NULL, true)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAdd(md, PS_LIST_TAIL, "track", PS_DATA_BOOL, NULL, true)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = domeObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (!object->az == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->open == true) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->light == true) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->track == true) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        telescopeRow    *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "guide", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "alt", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "az", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = telescopeObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->guide, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->alt == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->az == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        summitExpRow    *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = summitExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        pzPendingExpRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = pzPendingExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        pzPendingImfileRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "bytes", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "md5sum", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = pzPendingImfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bytes == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->md5sum, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        newExpRow       *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = newExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        newImfileRow    *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = newImfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        rawDetrendExpRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "exp_time", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "background", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = rawDetrendExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->airmass == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exp_time == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->background == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        rawScienceExpRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "exp_time", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "background", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = rawScienceExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->airmass == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exp_time == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->background == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        rawImfileRow    *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = rawImfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        p1PendingExpRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "exp_time", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "background", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = p1PendingExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->airmass == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exp_time == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->background == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p1_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        p2PendingExpRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "ra", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "exp_time", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "background", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = p2PendingExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->airmass == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exp_time == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->background == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p1_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p2_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        p2PendingImfileRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = p2PendingImfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p1_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p2_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        p2DoneExpRow    *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "exp_time", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "background", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = p2DoneExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->airmass == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exp_time == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->background == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p1_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p2_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        p2DoneImfileRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "p1_version", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = p2DoneImfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p1_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p2_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        p3PendingExpRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "exp_time", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "background", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "p2_version", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "p3_version", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = p3PendingExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->airmass == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exp_time == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->background == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p2_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->p3_version == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        detRunRow       *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "det_type", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = detRunObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->det_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        detInputExpRow  *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "exp_time", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "background", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = detInputExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->airmass == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exp_time == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->background == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        detProcessedImfileRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = detProcessedImfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        detStackedImfileRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = detStackedImfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        detNormalizedImfileRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = detNormalizedImfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        detMasterFrameRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "comment", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = detMasterFrameObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->comment, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        detMasterImfileRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = detMasterImfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        detResidImfileAnalysisRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "b1_uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "b2_uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = detResidImfileAnalysisObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->b1_uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->b2_uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        detResidExpAnalysisRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAdd(md, PS_LIST_TAIL, "accept", PS_DATA_BOOL, NULL, true)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = detResidExpAnalysisObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (!object->det_id == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->accept == true) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /branches/jh_branches/jhoblitt/ippdb/tests/pop.c
===================================================================
--- /branches/jh_branches/jhoblitt/ippdb/tests/pop.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/ippdb/tests/pop.c	(revision 22254)
@@ -0,0 +1,691 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+
+int main ()
+{
+    {
+        psDB            *dbh;
+        psF32           temp01;
+        psF32           humi01;
+        psF32           temp02;
+        psF32           humi02;
+        psF32           temp03;
+        psF32           humi03;
+        psF32           pressure;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!weatherPop(dbh, &temp01, &humi01, &temp02, &humi02, &temp03, &humi03, &pressure)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            filter[256];
+        psF64           trans;
+        psS32           nstars;
+        psF64           ra;
+        psF64           decl;
+        psF32           exptime;
+        psF64           sky_bright;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_transparencyPop(dbh, (char **)&filter, &trans, &nstars, &ra, &decl, &exptime, &sky_bright)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            disperser_id[256];
+        psF32           atmcomp1;
+        psF32           atmcomp2;
+        psF32           atmcomp3;
+        psS32           nstars;
+        psF64           ra;
+        psF64           decl;
+        psF32           exptime;
+        psF64           sky_bright;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_absorptionPop(dbh, (char **)&disperser_id, &atmcomp1, &atmcomp2, &atmcomp3, &nstars, &ra, &decl, &exptime, &sky_bright)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            disperser_id[256];
+        psF32           atmcomp1;
+        psF32           atmcomp2;
+        psF32           atmcomp3;
+        psF32           continuum;
+        psF32           exptime;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_emissionPop(dbh, (char **)&disperser_id, &atmcomp1, &atmcomp2, &atmcomp3, &continuum, &exptime)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psF32           sigmax;
+        psF32           sigmay;
+        psF32           fwhm;
+        psF64           ra;
+        psF64           decl;
+        psF32           expttime;
+        char            telescope_id[256];
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!dimmPop(dbh, &sigmax, &sigmay, &fwhm, &ra, &decl, &expttime, (char **)&telescope_id)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psF64           sky_bright;
+        psF64           sky_var;
+        psF64           ra;
+        psF64           decl;
+        psF32           fov_x;
+        psF32           fov_y;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_irPop(dbh, &sky_bright, &sky_var, &ra, &decl, &fov_x, &fov_y)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psF32           az;
+        bool            open;
+        bool            light;
+        bool            track;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!domePop(dbh, &az, &open, &light, &track)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            guide[256];
+        psF32           alt;
+        psF32           az;
+        psF64           ra;
+        psF64           decl;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!telescopePop(dbh, (char **)&guide, &alt, &az, &ra, &decl)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            exp_id[256];
+        char            camera[256];
+        char            telescope[256];
+        char            exp_type[256];
+        char            uri[256];
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!summitExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, (char **)&uri)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            exp_id[256];
+        char            camera[256];
+        char            telescope[256];
+        char            exp_type[256];
+        psS32           imfiles;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            exp_id[256];
+        psS32           bytes;
+        char            md5sum[256];
+        char            class[256];
+        char            class_id[256];
+        char            uri[256];
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingImfilePop(dbh, (char **)&exp_id, &bytes, (char **)&md5sum, (char **)&class, (char **)&class_id, (char **)&uri)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            exp_id[256];
+        char            camera[256];
+        char            telescope[256];
+        char            exp_type[256];
+        psS32           imfiles;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            exp_id[256];
+        char            class[256];
+        char            class_id[256];
+        char            uri[256];
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newImfilePop(dbh, (char **)&exp_id, (char **)&class, (char **)&class_id, (char **)&uri)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            exp_id[256];
+        char            camera[256];
+        char            telescope[256];
+        char            exp_type[256];
+        psS32           imfiles;
+        char            filter[256];
+        psF32           airmass;
+        psF64           ra;
+        psF64           decl;
+        psF32           exp_time;
+        psF64           background;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawDetrendExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles, (char **)&filter, &airmass, &ra, &decl, &exp_time, &background)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            exp_id[256];
+        char            camera[256];
+        char            telescope[256];
+        char            exp_type[256];
+        psS32           imfiles;
+        char            filter[256];
+        psF32           airmass;
+        psF64           ra;
+        psF64           decl;
+        psF32           exp_time;
+        psF64           background;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawScienceExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles, (char **)&filter, &airmass, &ra, &decl, &exp_time, &background)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            exp_id[256];
+        char            class[256];
+        char            class_id[256];
+        char            uri[256];
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawImfilePop(dbh, (char **)&exp_id, (char **)&class, (char **)&class_id, (char **)&uri)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            exp_id[256];
+        char            camera[256];
+        char            telescope[256];
+        char            exp_type[256];
+        psS32           imfiles;
+        char            filter[256];
+        psF32           airmass;
+        psF64           ra;
+        psF64           decl;
+        psF32           exp_time;
+        psF64           background;
+        char            recipe[256];
+        psS32           p1_version;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p1PendingExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles, (char **)&filter, &airmass, &ra, &decl, &exp_time, &background, (char **)&recipe, &p1_version)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            exp_id[256];
+        char            camera[256];
+        char            telescope[256];
+        char            exp_type[256];
+        psS32           imfiles;
+        char            filter[256];
+        psF32           airmass;
+        psF32           ra;
+        psF64           decl;
+        psF64           exp_time;
+        psF64           background;
+        char            recipe[256];
+        psS32           p1_version;
+        psS32           p2_version;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2PendingExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles, (char **)&filter, &airmass, &ra, &decl, &exp_time, &background, (char **)&recipe, &p1_version, &p2_version)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            exp_id[256];
+        char            class_id[256];
+        char            uri[256];
+        char            recipe[256];
+        psS32           p1_version;
+        psS32           p2_version;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2PendingImfilePop(dbh, (char **)&exp_id, (char **)&class_id, (char **)&uri, (char **)&recipe, &p1_version, &p2_version)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            exp_id[256];
+        char            camera[256];
+        char            telescope[256];
+        char            exp_type[256];
+        psS32           imfiles;
+        char            filter[256];
+        psF32           airmass;
+        psF64           ra;
+        psF64           decl;
+        psF32           exp_time;
+        psF64           background;
+        char            recipe[256];
+        psS32           p1_version;
+        psS32           p2_version;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2DoneExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles, (char **)&filter, &airmass, &ra, &decl, &exp_time, &background, (char **)&recipe, &p1_version, &p2_version)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            exp_id[256];
+        char            class_id[256];
+        char            uri[256];
+        char            recipe[256];
+        psS32           p1_version;
+        psS32           p2_version;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2DoneImfilePop(dbh, (char **)&exp_id, (char **)&class_id, (char **)&uri, (char **)&recipe, &p1_version, &p2_version)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        char            exp_id[256];
+        char            camera[256];
+        char            exp_type[256];
+        psS32           imfiles;
+        char            filter[256];
+        psF32           airmass;
+        psF64           ra;
+        psF64           decl;
+        psF32           exp_time;
+        psF64           background;
+        char            recipe[256];
+        psS32           p2_version;
+        psS32           p3_version;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p3PendingExpPop(dbh, (char **)&exp_id, (char **)&camera, (char **)&exp_type, &imfiles, (char **)&filter, &airmass, &ra, &decl, &exp_time, &background, (char **)&recipe, &p2_version, &p3_version)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psS32           iteration;
+        char            det_type[256];
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detRunPop(dbh, &iteration, (char **)&det_type)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psS32           det_id;
+        psS32           iteration;
+        char            exp_id[256];
+        char            camera[256];
+        char            telescope[256];
+        char            exp_type[256];
+        psS32           imfiles;
+        char            filter[256];
+        psF32           airmass;
+        psF64           ra;
+        psF64           decl;
+        psF32           exp_time;
+        psF64           background;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detInputExpPop(dbh, &det_id, &iteration, (char **)&exp_id, (char **)&camera, (char **)&telescope, (char **)&exp_type, &imfiles, (char **)&filter, &airmass, &ra, &decl, &exp_time, &background)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psS32           det_id;
+        char            exp_id[256];
+        char            class_id[256];
+        char            uri[256];
+        char            recipe[256];
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detProcessedImfilePop(dbh, &det_id, (char **)&exp_id, (char **)&class_id, (char **)&uri, (char **)&recipe)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psS32           det_id;
+        psS32           iteration;
+        char            class_id[256];
+        char            uri[256];
+        char            recipe[256];
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detStackedImfilePop(dbh, &det_id, &iteration, (char **)&class_id, (char **)&uri, (char **)&recipe)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psS32           det_id;
+        psS32           iteration;
+        char            class_id[256];
+        char            uri[256];
+        char            recipe[256];
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detNormalizedImfilePop(dbh, &det_id, &iteration, (char **)&class_id, (char **)&uri, (char **)&recipe)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psS32           det_id;
+        psS32           iteration;
+        char            comment[256];
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detMasterFramePop(dbh, &det_id, &iteration, (char **)&comment)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psS32           det_id;
+        char            class_id[256];
+        char            uri[256];
+        char            recipe[256];
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detMasterImfilePop(dbh, &det_id, (char **)&class_id, (char **)&uri, (char **)&recipe)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psS32           det_id;
+        psS32           iteration;
+        char            exp_id[256];
+        char            class_id[256];
+        char            recipe[256];
+        char            uri[256];
+        char            b1_uri[256];
+        char            b2_uri[256];
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidImfileAnalysisPop(dbh, &det_id, &iteration, (char **)&exp_id, (char **)&class_id, (char **)&recipe, (char **)&uri, (char **)&b1_uri, (char **)&b2_uri)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psS32           det_id;
+        psS32           iteration;
+        char            exp_id[256];
+        char            recipe[256];
+        bool            accept;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidExpAnalysisPop(dbh, &det_id, &iteration, (char **)&exp_id, (char **)&recipe, &accept)) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /branches/jh_branches/jhoblitt/ippdb/tests/popfits.c
===================================================================
--- /branches/jh_branches/jhoblitt/ippdb/tests/popfits.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/ippdb/tests/popfits.c	(revision 22254)
@@ -0,0 +1,818 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#define TMP_FILENAME "./blargh"
+
+int main ()
+{
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!weatherPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_transparencyPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_absorptionPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_emissionPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!dimmPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_irPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!domePopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!telescopePopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!summitExpPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingExpPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingImfilePopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newExpPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newImfilePopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawDetrendExpPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawScienceExpPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawImfilePopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p1PendingExpPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2PendingExpPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2PendingImfilePopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2DoneExpPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2DoneImfilePopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p3PendingExpPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detRunPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detInputExpPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detProcessedImfilePopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detStackedImfilePopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detNormalizedImfilePopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detMasterFramePopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detMasterImfilePopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidImfileAnalysisPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidExpAnalysisPopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /branches/jh_branches/jhoblitt/ippdb/tests/popobject.c
===================================================================
--- /branches/jh_branches/jhoblitt/ippdb/tests/popobject.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/ippdb/tests/popobject.c	(revision 22254)
@@ -0,0 +1,566 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+
+int main ()
+{
+    {
+        psDB            *dbh;
+        weatherRow      *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = weatherPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        skyp_transparencyRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = skyp_transparencyPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        skyp_absorptionRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = skyp_absorptionPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        skyp_emissionRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = skyp_emissionPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        dimmRow         *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = dimmPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        skyp_irRow      *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = skyp_irPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        domeRow         *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = domePopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        telescopeRow    *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = telescopePopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        summitExpRow    *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = summitExpPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        pzPendingExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = pzPendingExpPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        pzPendingImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = pzPendingImfilePopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        newExpRow       *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = newExpPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        newImfileRow    *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = newImfilePopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        rawDetrendExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = rawDetrendExpPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        rawScienceExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = rawScienceExpPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        rawImfileRow    *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = rawImfilePopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        p1PendingExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = p1PendingExpPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        p2PendingExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = p2PendingExpPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        p2PendingImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = p2PendingImfilePopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        p2DoneExpRow    *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = p2DoneExpPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        p2DoneImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = p2DoneImfilePopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        p3PendingExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = p3PendingExpPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detRunRow       *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detRunPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detInputExpRow  *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detInputExpPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detProcessedImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detProcessedImfilePopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detStackedImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detStackedImfilePopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detNormalizedImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detNormalizedImfilePopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detMasterFrameRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detMasterFramePopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detMasterImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detMasterImfilePopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detResidImfileAnalysisRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detResidImfileAnalysisPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detResidExpAnalysisRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detResidExpAnalysisPopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /branches/jh_branches/jhoblitt/ippdb/tests/selectrowsfits.c
===================================================================
--- /branches/jh_branches/jhoblitt/ippdb/tests/selectrowsfits.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/ippdb/tests/selectrowsfits.c	(revision 22254)
@@ -0,0 +1,692 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+
+#define TMP_FILENAME "./blargh"
+
+int main ()
+{
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!weatherSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_transparencySelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_absorptionSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_emissionSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!dimmSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!skyp_irSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!domeSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!telescopeSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!summitExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingImfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newImfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawDetrendExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawScienceExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawImfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p1PendingExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2PendingExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2PendingImfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2DoneExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p2DoneImfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!p3PendingExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detRunSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detInputExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detProcessedImfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detStackedImfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detNormalizedImfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detMasterFrameSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detMasterImfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidImfileAnalysisSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidExpAnalysisSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /branches/jh_branches/jhoblitt/ippdb/tests/testsuite.at
===================================================================
--- /branches/jh_branches/jhoblitt/ippdb/tests/testsuite.at	(revision 22254)
+++ /branches/jh_branches/jhoblitt/ippdb/tests/testsuite.at	(revision 22254)
@@ -0,0 +1,158 @@
+m4_version_prereq([2.59])
+
+AT_INIT
+
+###
+AT_SETUP([Alloc()])
+AT_KEYWORDS([Alloc])
+
+AT_CHECK([alloc])
+
+AT_CLEANUP
+
+###
+AT_SETUP([Init()])
+AT_KEYWORDS([Init])
+
+AT_CHECK([init])
+
+AT_CLEANUP
+
+###
+AT_SETUP([Cleanup()])
+AT_KEYWORDS([Cleanup])
+
+AT_CHECK([cleanup])
+
+AT_CLEANUP
+
+###
+AT_SETUP([CreateTable()])
+AT_KEYWORDS([CreateTable])
+
+# make sure the table is not there.
+AT_CHECK([dbcleanup])
+AT_CHECK([createtable])
+AT_CHECK([dbcleanup])
+
+AT_CLEANUP
+
+###
+AT_SETUP([DropTable()])
+AT_KEYWORDS([DropTable])
+
+# make sure the table is not there.
+AT_CHECK([dbsetup])
+AT_CHECK([droptable])
+AT_CHECK([dbcleanup])
+
+AT_CLEANUP
+
+###
+AT_SETUP([Insert()])
+AT_KEYWORDS([Insert])
+
+AT_CHECK([dbsetup])
+AT_CHECK([insert])
+AT_CHECK([dbcleanup])
+
+AT_CLEANUP
+
+###
+AT_SETUP([Pop()])
+AT_KEYWORDS([Pop])
+
+AT_CHECK([dbsetup])
+# run insert so there is a row in the table
+AT_CHECK([insert])
+AT_CHECK([pop])
+AT_CHECK([dbcleanup])
+
+AT_CLEANUP
+
+###
+AT_SETUP([InsertObject()])
+AT_KEYWORDS([InsertObject])
+
+AT_CHECK([dbsetup])
+AT_CHECK([insertobject])
+AT_CHECK([dbcleanup])
+
+AT_CLEANUP
+
+###
+AT_SETUP([PopObject()])
+AT_KEYWORDS([PopObject])
+
+AT_CHECK([dbsetup])
+# run insert so there is a row in the table
+AT_CHECK([insert])
+AT_CHECK([popobject])
+AT_CHECK([dbcleanup])
+
+AT_CLEANUP
+
+###
+AT_SETUP([InsertFits()])
+AT_KEYWORDS([InsertFits])
+
+AT_CHECK([dbsetup])
+# run insert so there is a row in the table
+AT_CHECK([insert])
+# run popfis so there is afits file to read
+AT_CHECK([popfits])
+AT_CHECK([insertfits])
+AT_CHECK([dbcleanup])
+if [ test -f "blargh" ] ; then
+    rm -f "blargh"
+fi
+
+AT_CLEANUP
+
+###
+AT_SETUP([PopFits()])
+AT_KEYWORDS([PopFits])
+
+AT_CHECK([dbsetup])
+# run insert so there is a row in the table
+AT_CHECK([insert])
+AT_CHECK([popfits])
+AT_CHECK([dbcleanup])
+if [ test -f "blargh" ] ; then
+    rm -f "blargh"
+fi
+
+AT_CLEANUP
+
+###
+AT_SETUP([SelectRowsFits()])
+AT_KEYWORDS([SelectRowsFits])
+
+AT_CHECK([dbsetup])
+# run insert so there is a row in the table
+AT_CHECK([insert])
+AT_CHECK([selectrowsfits])
+AT_CHECK([dbcleanup])
+if [ test -f "blargh" ] ; then
+    rm -f "blargh"
+fi
+
+AT_CLEANUP
+
+###
+AT_SETUP([MetadataFromObject()])
+AT_KEYWORDS([MetadataFromObject])
+
+AT_CHECK([metadatafromobject])
+
+AT_CLEANUP
+
+###
+AT_SETUP([ObjectFromMetadata()])
+AT_KEYWORDS([ObjectFromMetadata])
+
+AT_CHECK([objectfrommetadata])
+
+AT_CLEANUP
+
+###
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/INSTALL
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/INSTALL	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/INSTALL	(revision 22254)
@@ -0,0 +1,8 @@
+Quick Installation
+
+    ./configure
+    make
+    make check
+    make install
+
+Run "configure --help" for additional options.
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/LICENSE
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/LICENSE	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/LICENSE	(revision 22254)
@@ -0,0 +1,23 @@
+Copyright (c) 2004 Nik Clayton
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/Makefile.am	(revision 22254)
@@ -0,0 +1,5 @@
+SUBDIRS  = src
+SUBDIRS += tests
+
+prove:
+	prove -v -r
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/README
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/README	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/README	(revision 22254)
@@ -0,0 +1,11 @@
+NAME
+     tap -- write tests that implement the Test Anything Protocol
+
+SYNOPSIS
+     #include <tap.h>
+
+DESCRIPTION
+     The tap library provides functions for writing test scripts that produce
+     output consistent with the Test Anything Protocol.  A test harness that
+     parses this protocol can run these tests and produce useful reports indi-
+     cating their success or failure.
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/bootstrap.sh
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/bootstrap.sh	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/bootstrap.sh	(revision 22254)
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+set -x
+aclocal19 -I /usr/local/share/aclocal || aclocal || exit 1
+autoheader259 || autoheader || exit 1
+libtoolize15 -c -f || libtoolize -c -f || glibtoolize -c -f || exit 1
+automake19 -a -c || automake -a -c || exit 1
+autoconf259 || autoconf || exit 1
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/compile
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/compile	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/compile	(revision 22254)
@@ -0,0 +1,142 @@
+#! /bin/sh
+# Wrapper for compilers which do not understand `-c -o'.
+
+scriptversion=2004-10-12.08
+
+# Copyright (C) 1999, 2000, 2003, 2004 Free Software Foundation, Inc.
+# Written by Tom Tromey <tromey@cygnus.com>.
+#
+# 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; either version 2, or (at your option)
+# any later version.
+#
+# 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 this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# This file is maintained in Automake, please report
+# bugs to <bug-automake@gnu.org> or send patches to
+# <automake-patches@gnu.org>.
+
+case $1 in
+  '')
+     echo "$0: No command.  Try \`$0 --help' for more information." 1>&2
+     exit 1;
+     ;;
+  -h | --h*)
+    cat <<\EOF
+Usage: compile [--help] [--version] PROGRAM [ARGS]
+
+Wrapper for compilers which do not understand `-c -o'.
+Remove `-o dest.o' from ARGS, run PROGRAM with the remaining
+arguments, and rename the output as expected.
+
+If you are trying to build a whole package this is not the
+right script to run: please start by reading the file `INSTALL'.
+
+Report bugs to <bug-automake@gnu.org>.
+EOF
+    exit 0
+    ;;
+  -v | --v*)
+    echo "compile $scriptversion"
+    exit 0
+    ;;
+esac
+
+ofile=
+cfile=
+eat=
+
+for arg
+do
+  if test -n "$eat"; then
+    eat=
+  else
+    case $1 in
+      -o)
+	# configure might choose to run compile as `compile cc -o foo foo.c'.
+	# So we strip `-o arg' only if arg is an object.
+	eat=1
+	case $2 in
+	  *.o | *.obj)
+	    ofile=$2
+	    ;;
+	  *)
+	    set x "$@" -o "$2"
+	    shift
+	    ;;
+	esac
+	;;
+      *.c)
+	cfile=$1
+	set x "$@" "$1"
+	shift
+	;;
+      *)
+	set x "$@" "$1"
+	shift
+	;;
+    esac
+  fi
+  shift
+done
+
+if test -z "$ofile" || test -z "$cfile"; then
+  # If no `-o' option was seen then we might have been invoked from a
+  # pattern rule where we don't need one.  That is ok -- this is a
+  # normal compilation that the losing compiler can handle.  If no
+  # `.c' file was seen then we are probably linking.  That is also
+  # ok.
+  exec "$@"
+fi
+
+# Name of file we expect compiler to create.
+cofile=`echo "$cfile" | sed -e 's|^.*/||' -e 's/\.c$/.o/'`
+
+# Create the lock directory.
+# Note: use `[/.-]' here to ensure that we don't use the same name
+# that we are using for the .o file.  Also, base the name on the expected
+# object file name, since that is what matters with a parallel build.
+lockdir=`echo "$cofile" | sed -e 's|[/.-]|_|g'`.d
+while true; do
+  if mkdir "$lockdir" >/dev/null 2>&1; then
+    break
+  fi
+  sleep 1
+done
+# FIXME: race condition here if user kills between mkdir and trap.
+trap "rmdir '$lockdir'; exit 1" 1 2 15
+
+# Run the compile.
+"$@"
+ret=$?
+
+if test -f "$cofile"; then
+  mv "$cofile" "$ofile"
+elif test -f "${cofile}bj"; then
+  mv "${cofile}bj" "$ofile"
+fi
+
+rmdir "$lockdir"
+exit $ret
+
+# Local Variables:
+# mode: shell-script
+# sh-indentation: 2
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-end: "$"
+# End:
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/configure.in
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/configure.in	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/configure.in	(revision 22254)
@@ -0,0 +1,52 @@
+AC_INIT(tap, 1.01)
+AC_CONFIG_SRCDIR(src/tap.c)
+AM_INIT_AUTOMAKE([foreign])
+AC_PROG_CC
+AC_PROG_LIBTOOL
+AC_PROG_INSTALL
+
+# Checks for libraries
+case "$host" in
+	*-*-*freebsd4*)
+		LDFLAGS="$LDFLAGS -pthread"
+		HAVE_LIBPTHREAD=1
+		;;
+	*)
+		AC_CHECK_LIB(pthread, main)
+		;;
+esac
+
+# Checks for header files
+AC_HEADER_STDC
+AC_CHECK_HEADERS([stdlib.h])
+AC_CHECK_HEADERS([pthread.h])
+
+# Checks for  typedefs, structures, and compiler characteristics.
+AC_C_CONST
+
+# Checks for library functions.
+AC_FUNC_VPRINTF
+AC_CHECK_FUNCS([atexit])
+
+AC_CONFIG_FILES([Makefile
+		 src/Makefile
+		 tests/Makefile
+		 tests/diag/Makefile
+		 tests/fail/Makefile
+		 tests/ok/Makefile
+		 tests/ok/ok-hash/Makefile
+		 tests/ok/ok-numeric/Makefile
+		 tests/ok/ok/Makefile
+		 tests/pass/Makefile
+		 tests/plan/Makefile
+		 tests/plan/no-tests/Makefile
+		 tests/plan/no_plan/Makefile
+		 tests/plan/not-enough-tests/Makefile
+		 tests/plan/sane/Makefile
+		 tests/plan/skip_all/Makefile
+		 tests/plan/too-many-plans/Makefile
+		 tests/plan/too-many-tests/Makefile
+		 tests/skip/Makefile
+		 tests/todo/Makefile
+		])
+AC_OUTPUT
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/src/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/src/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/src/Makefile.am	(revision 22254)
@@ -0,0 +1,7 @@
+lib_LTLIBRARIES = libtap.la
+libtap_la_SOURCES = tap.c tap.h
+
+man_MANS = tap.3
+EXTRA_DIST = $(man_MANS)
+
+include_HEADERS = tap.h
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/src/tap.3
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/src/tap.3	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/src/tap.3	(revision 22254)
@@ -0,0 +1,380 @@
+.Dd December 20, 2004
+.Os
+.Dt TAP 3
+.Sh NAME
+.Nm tap
+.Nd write tests that implement the Test Anything Protocol
+.Sh SYNOPSIS
+.In tap.h
+.Sh DESCRIPTION
+The
+.Nm
+library provides functions for writing test scripts that produce output
+consistent with the Test Anything Protocol.  A test harness that parses
+this protocol can run these tests and produce useful reports indicating
+their success or failure.
+.Ss PRINTF STRINGS
+In the descriptions that follow, for any function that takes as the
+last two parameters
+.Dq Fa char * , Fa ...
+it can be assumed that the
+.Fa char *
+is a
+.Fn printf
+-like format string, and the optional arguments are values to be placed
+in that string.
+.Ss TEST PLANS
+.Bl -tag -width indent
+.It Xo
+.Ft int
+.Fn plan_tests "unsigned int"
+.Xc
+.It Xo
+.Ft int
+.Fn plan_no_plan "void"
+.Xc
+.It Xo
+.Ft int
+.Fn plan_skip_all "char *" "..."
+.Xc
+.El
+.Pp
+You must first specify a test plan.  This indicates how many tests you
+intend to run, and allows the test harness to notice if any tests were
+missed, or if the test program exited prematurely.
+.Pp
+To do this, use
+.Fn plan_tests ,
+which returns the number of planned tests.  The function will cause
+your program to exit prematurely if you specify 0 tests.
+.Pp
+In some situations you may not know how many tests you will be running, or
+you are developing your test program, and do not want to update the
+.Fn plan_tests
+parameter every time you make a change.  For those situations use
+.Fn plan_no_plan .
+It returns 1, and indicates to the test harness that an indeterminate number
+of tests will be run.
+.Pp
+Both
+.Fn plan_tests
+and
+.Fn plan_no_plan
+will cause your test program to exit prematurely with a diagnostic
+message if they are called more than once.
+.Pp
+If your test program detects at run time that some required functionality
+is missing (for example, it relies on a database connection which is not
+present, or a particular configuration option that has not been included
+in the running kernel) use
+.Fn plan_skip_all ,
+passing as parameters a string to display indicating the reason for skipping
+the tests.
+.Ss SIMPLE TESTS
+.Bl -tag -width indent
+.It Xo
+.Ft unsigned int
+.Fn ok "expression" "char *" "..."
+.Xc
+.It Xo
+.Ft unsigned int
+.Fn ok1 "expression"
+.Xc
+.It Xo
+.Ft unsigned int
+.Fn pass "char *" "..."
+.Xc
+.It Xo
+.Ft unsigned int
+.Fn fail "char *" "..."
+.Xc
+.El
+.Pp
+Tests are implemented as expressions checked by calls to the
+.Fn ok
+and
+.Fn ok1
+macros.  In both cases
+.Fa expression
+should evaluate to true if the test succeeded.
+.Pp
+.Fn ok
+allows you to specify a name, or comment, describing the test which will
+be included in the output.
+.Fn ok1
+is for those times when the expression to be tested is self
+explanatory and does not need an associated comment.  In those cases
+the test expression becomes the comment.
+.Pp
+These four calls are equivalent:
+.Bd -literal -offset indent
+int i = 5;
+
+ok(i == 5, "i equals 5");      /* Overly verbose */
+ok(i == 5, "i equals %d", i);  /* Just to demonstrate printf-like
+                                  behaviour of the test name */
+ok(i == 5, "i == 5");          /* Needless repetition */
+ok1(i == 5);                   /* Just right */
+.Ed
+.Pp
+It is good practice to ensure that the test name describes the meaning
+behind the test rather than what you are testing.  Viz
+.Bd -literal -offset indent
+ok(db != NULL, "db is not NULL");            /* Not bad, but */
+ok(db != NULL, "Database conn. succeeded");  /* this is better */
+.Ed
+.Pp
+.Fn ok
+and
+.Fn ok1
+return 1 if the expression evaluated to true, and 0 if it evaluated to
+false.  This lets you chain calls from
+.Fn ok
+to
+.Fn diag
+to only produce diagnostic output if the test failed.  For example, this
+code will include diagnostic information about why the database connection
+failed, but only if the test failed.
+.Bd -literal -offset indent
+ok(db != NULL, "Database conn. succeeded") ||
+    diag("Database error code: %d", dberrno);
+.Ed
+.Pp
+You also have
+.Fn pass
+and
+.Fn fail .
+From the Test::More documentation:
+.Bd -literal -offset indent
+Sometimes you just want to say that the tests have passed.
+Usually the case is you've got some complicated condition
+that is difficult to wedge into an ok().  In this case,
+you can simply use pass() (to declare the test ok) or fail
+(for not ok).
+
+Use these very, very, very sparingly.
+.Ed
+.Pp
+These are synonyms for ok(1, ...) and ok(0, ...).
+.Ss SKIPPING TESTS
+.Bl -tag -width indent
+.It Xo
+.Ft int
+.Fn skip "unsigned int" "char *" "..."
+.Xc
+.It Xo
+.Fn skip_start "expression" "unsigned int" "char *" "..."
+.Xc
+.It Xo
+.Fn skip_end
+.Xc
+.El
+.Pp
+Sets of tests can be skipped.  Ordinarily you would do this because
+the test can't be run in this particular testing environment.
+.Pp
+For example, suppose some tests should be run as root.  If the test is
+not being run as root then the tests should be skipped.  In this 
+implementation, skipped tests are flagged as being ok, with a special
+message indicating that they were skipped.  It is your responsibility
+to ensure that the number of tests skipped (the first parameter to
+.Fn skip )
+is correct for the number of tests to skip.
+.Pp
+One way of implementing this is with a
+.Dq do { } while(0);
+loop, or an
+.Dq if( ) { } else { }
+construct, to ensure that there are no additional side effects from the
+skipped tests.
+.Bd -literal -offset indent
+if(getuid() != 0) {
+        skip(1, "because test only works as root");
+} else {
+        ok(do_something_as_root() == 0, "Did something as root");
+}
+.Ed
+.Pp
+Two macros are provided to assist with this.  The previous example could
+be re-written as follows.
+.Bd -literal -offset indent
+skip_start(getuid() != 0, 1, "because test only works as root");
+
+ok(do_something_as_root() == 0, "Did something as root");
+
+skip_end();
+.Ed
+.Ss MARKING TESTS AS Dq TODO
+.Bl -tag -width indent
+.It Xo
+.Ft void
+.Fn todo_start "char *" "..."
+.Xc
+.It Xo
+.Ft void
+.Fn todo_end "void"
+.Xc
+.El
+.Pp
+Sets of tests can be flagged as being
+.Dq TODO .
+These are tests that you expect to fail, probably because you haven't
+fixed a bug, or finished a new feature yet.  These tests will still be
+run, but with additional output that indicates that they are expected
+to fail.  Should a test start to succeed unexpectedly, tools like
+.Xr prove 1
+will indicate this, and you can move the test out of the todo
+block.  This is much more useful than simply commenting out (or
+.Dq #ifdef 0 ... #endif )
+the tests.
+.Bd -literal -offset indent
+todo_start("dwim() not returning true yet");
+
+ok(dwim(), "Did what the user wanted");
+
+todo_end();
+.Ed
+.Pp
+Should
+.Fn dwim
+ever start succeeding you will know about it as soon as you run the
+tests.  Note that
+.Em unlike
+the
+.Fn skip_*
+family, additional code between
+.Fn todo_start
+and
+.Fn todo_end
+.Em is
+executed.
+.Ss SKIP vs. TODO
+From the Test::More documentation;
+.Bd -literal -offset indent
+If it's something the user might not be able to do, use SKIP.
+This includes optional modules that aren't installed, running
+under an OS that doesn't have some feature (like fork() or
+symlinks), or maybe you need an Internet connection and one
+isn't available.
+
+If it's something the programmer hasn't done yet, use TODO.
+This is for any code you haven't written yet, or bugs you have
+yet to fix, but want to put tests in your testing script 
+(always a good idea).
+.Ed
+.Ss DIAGNOSTIC OUTPUT
+.Bl -tag -width indent
+.It Xo
+.Fr unsigned int
+.Fn diag "char *" "..."
+.Xc
+.El
+.Pp
+If your tests need to produce diagnostic output, use
+.Fn diag .
+It ensures that the output will not be considered by the TAP test harness.
+.Fn diag
+adds the necessary trailing
+.Dq \en
+for you.
+.Bd -literal -offset indent
+diag("Expected return code 0, got return code %d", rcode);
+.Ed
+.Pp
+.Fn diag
+always returns 0.
+.Ss EXIT STATUS
+.Bl -tag -width indent
+.It Xo
+.Fr int
+.Fn exit_status void
+.Xc
+.El
+.Pp
+For maximum compatability your test program should return a particular
+exit code.  This is calculated by
+.Fn exit_status
+so it is sufficient to always return from
+.Fn main
+with either
+.Dq return exit_status();
+or
+.Dq exit(exit_status());
+as appropriate.
+.Sh ENVIRONMENT
+The following environment variables affect
+.Nm .
+.Bl -tag -width indent
+.It Ev HARNESS_ACTIVE
+Causes an extra
+.Dq \en
+to be printed before any diagnostic failure output generated by
+.Nm .
+This variable is normally set if tests are being run under Perl's
+Test::Harness.
+.El
+.Sh EXAMPLES
+The
+.Pa tests
+directory in the source distribution contains numerous tests of
+.Nm
+functionality, written using
+.Nm .
+Examine them for examples of how to construct test suites.
+.Sh COMPATABILITY
+.Nm
+strives to be compatible with the Perl Test::More and Test::Harness 
+modules.  The test suite verifies that
+.Nm
+is bug-for-bug compatible with their behaviour.  This is why some
+functions which would more naturally return nothing return constant
+values.
+.Pp
+If the
+.Lb libpthread
+is found at compile time,
+.Nm
+.Em should
+be thread safe.  Indications to the contrary (and test cases that expose
+incorrect behaviour) are very welcome.
+.Sh SEE ALSO
+.Xr Test::More 1 ,
+.Xr Test::Harness 1 ,
+.Xr prove 1
+.Sh STANDARDS
+.Nm
+requires a
+.St -isoC-99
+compiler.  Some of the
+.Nm
+functionality is implemented as variadic macros, and that functionality
+was not formally codified until C99.  Patches to use
+.Nm
+with earlier compilers that have their own implementation of variadic
+macros will be gratefully received.
+.Sh HISTORY
+.Nm
+was written to help improve the quality and coverage of the FreeBSD
+regression test suite, and released in the hope that others find it
+a useful tool to help improve the quality of their code.
+.Sh AUTHORS
+.An "Nik Clayton" Aq nik@ngo.org.uk ,
+.Aq nik@FreeBSD.org
+.Pp
+.Nm
+would not exist without the efforts of
+.An "Michael G Schwern" Aq schqern@pobox.com ,
+.An "Andy Lester" Aq andy@petdance.com ,
+and the countless others who have worked on the Perl QA programme.
+.Sh BUGS
+Ideally, running the tests would have no side effects on the behaviour
+of the application you are testing.  However, it is not always possible
+to avoid them.  The following side effects of using
+.Nm
+are known.
+.Bl -bullet -offset indent
+.It
+stdout is set to unbuffered mode after calling any of the
+.Fn plan_*
+functions.
+.El
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/src/tap.c
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/src/tap.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/src/tap.c	(revision 22254)
@@ -0,0 +1,426 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "tap.h"
+
+static int no_plan = 0;
+static int skip_all = 0;
+static int have_plan = 0;
+static unsigned int test_count = 0; /* Number of tests that have been run */
+static unsigned int e_tests = 0; /* Expected number of tests to run */
+static unsigned int failures = 0; /* Number of tests that failed */
+static char *todo_msg = NULL;
+static char *todo_msg_fixed = "libtap malloc issue";
+static int todo = 0;
+static int test_died = 0;
+
+/* Encapsulate the pthread code in a conditional.  In the absence of
+   libpthread the code does nothing */
+#ifdef HAVE_LIBPTHREAD
+#include <pthread.h>
+static pthread_mutex_t M = PTHREAD_MUTEX_INITIALIZER;
+# define LOCK pthread_mutex_lock(&M);
+# define UNLOCK pthread_mutex_unlock(&M);
+#else
+# define LOCK
+# define UNLOCK
+#endif
+
+static void _expected_tests(unsigned int);
+static void _tap_init(void);
+static void _cleanup(void);
+
+/*
+ * Generate a test result.
+ *
+ * ok -- boolean, indicates whether or not the test passed.
+ * test_name -- the name of the test, may be NULL
+ * test_comment -- a comment to print afterwards, may be NULL
+ */
+unsigned int
+_gen_result(int ok, const char *func, char *file, unsigned int line, 
+	    char *test_name, ...)
+{
+	va_list ap;
+	char *local_test_name = NULL;
+	char *c;
+	int name_is_digits;
+
+	LOCK;
+
+	test_count++;
+
+	/* Start by taking the test name and performing any printf()
+	   expansions on it */
+	if(test_name != NULL) {
+		va_start(ap, test_name);
+		vasprintf(&local_test_name, test_name, ap);
+		va_end(ap);
+
+		/* Make sure the test name contains more than digits
+		   and spaces.  Emit an error message and exit if it
+		   does */
+		if(local_test_name) {
+			name_is_digits = 1;
+			for(c = local_test_name; *c != '\0'; c++) {
+				if(!isdigit(*c) && !isspace(*c)) {
+					name_is_digits = 0;
+					break;
+				}
+			}
+
+			if(name_is_digits) {
+				diag("    You named your test '%s'.  You shouldn't use numbers for your test names.", local_test_name);
+				diag("    Very confusing.");
+			}
+		}
+	}
+
+	if(!ok) {
+		printf("not ");
+		failures++;
+	}
+
+	printf("ok %d", test_count);
+
+	if(test_name != NULL) {
+		printf(" - ");
+
+		/* Print the test name, escaping any '#' characters it
+		   might contain */
+		if(local_test_name != NULL) {
+			flockfile(stdout);
+			for(c = local_test_name; *c != '\0'; c++) {
+				if(*c == '#')
+					fputc('\\', stdout);
+				fputc((int)*c, stdout);
+			}
+			funlockfile(stdout);
+		} else {	/* vasprintf() failed, use a fixed message */
+			printf("%s", todo_msg_fixed);
+		}
+	}
+
+	/* If we're in a todo_start() block then flag the test as being
+	   TODO.  todo_msg should contain the message to print at this
+	   point.  If it's NULL then asprintf() failed, and we should
+	   use the fixed message.
+
+	   This is not counted as a failure, so decrement the counter if
+	   the test failed. */
+	if(todo) {
+		printf(" # TODO %s", todo_msg ? todo_msg : todo_msg_fixed);
+		if(!ok)
+			failures--;
+	}
+
+	printf("\n");
+
+	if(!ok) {
+		if(getenv("HARNESS_ACTIVE") != NULL)
+			fputs("\n", stderr);
+
+		diag("    Failed %stest (%s:%s() at line %d)", 
+		     todo ? "(TODO) " : "", file, func, line);
+	}
+	free(local_test_name);
+
+	UNLOCK;
+
+	/* We only care (when testing) that ok is positive, but here we
+	   specifically only want to return 1 or 0 */
+	return ok ? 1 : 0;
+}
+
+/*
+ * Initialise the TAP library.  Will only do so once, however many times it's
+ * called.
+ */
+void
+_tap_init(void)
+{
+	static int run_once = 0;
+
+	if(!run_once) {
+		atexit(_cleanup);
+
+		/* stdout needs to be unbuffered so that the output appears
+		   in the same place relative to stderr output as it does 
+		   with Test::Harness */
+		setbuf(stdout, 0);
+		run_once = 1;
+	}
+}
+
+/*
+ * Note that there's no plan.
+ */
+int
+plan_no_plan(void)
+{
+
+	LOCK;
+
+	_tap_init();
+
+	if(have_plan != 0) {
+		fprintf(stderr, "You tried to plan twice!\n");
+		test_died = 1;
+		UNLOCK;
+		exit(255);
+	}
+
+	have_plan = 1;
+	no_plan = 1;
+
+	UNLOCK;
+
+	return 1;
+}
+
+/*
+ * Note that the plan is to skip all tests
+ */
+int
+plan_skip_all(char *reason)
+{
+
+	LOCK;
+
+	_tap_init();
+
+	skip_all = 1;
+
+	printf("1..0");
+
+	if(reason != NULL)
+		printf(" # Skip %s", reason);
+
+	printf("\n");
+
+	UNLOCK;
+
+	exit(0);
+}
+
+/*
+ * Note the number of tests that will be run.
+ */
+int
+plan_tests(unsigned int tests)
+{
+
+	LOCK;
+
+	_tap_init();
+
+	if(have_plan != 0) {
+		fprintf(stderr, "You tried to plan twice!\n");
+		test_died = 1;
+		UNLOCK;
+		exit(255);
+	}
+
+	if(tests == 0) {
+		fprintf(stderr, "You said to run 0 tests!  You've got to run something.\n");
+		test_died = 1;
+		UNLOCK;
+		exit(255);
+	}
+
+	have_plan = 1;
+
+	_expected_tests(tests);
+
+	UNLOCK;
+
+	return e_tests;
+}
+
+unsigned int
+diag(char *fmt, ...)
+{
+	va_list ap;
+
+	fputs("# ", stderr);
+
+	va_start(ap, fmt);
+	vfprintf(stderr, fmt, ap);
+	va_end(ap);
+
+	fputs("\n", stderr);
+
+	return 0;
+}
+
+void
+_expected_tests(unsigned int tests)
+{
+
+	printf("1..%d\n", tests);
+	e_tests = tests;
+}
+
+int
+skip(unsigned int n, char *fmt, ...)
+{
+	va_list ap;
+	char *skip_msg;
+
+	LOCK;
+
+	va_start(ap, fmt);
+	asprintf(&skip_msg, fmt, ap);
+	va_end(ap);
+
+	while(n-- > 0) {
+		test_count++;
+		printf("ok %d # skip %s\n", test_count, 
+		       skip_msg != NULL ? 
+		       skip_msg : "libtap():malloc() failed");
+	}
+
+	free(skip_msg);
+
+	UNLOCK;
+
+	return 1;
+}
+
+void
+todo_start(char *fmt, ...)
+{
+	va_list ap;
+
+	LOCK;
+
+	va_start(ap, fmt);
+	vasprintf(&todo_msg, fmt, ap);
+	va_end(ap);
+
+	todo = 1;
+
+	UNLOCK;
+}
+
+void
+todo_end(void)
+{
+
+	LOCK;
+
+	todo = 0;
+	free(todo_msg);
+
+	UNLOCK;
+}
+
+int
+exit_status(void)
+{
+	int r;
+
+	LOCK;
+
+	/* If there's no plan, just return the number of failures */
+	if(no_plan || !have_plan) {
+		UNLOCK;
+		return failures;
+	}
+
+	/* Ran too many tests?  Return the number of tests that were run
+	   that shouldn't have been */
+	if(e_tests < test_count) {
+		r = test_count - e_tests;
+		UNLOCK;
+		return r;
+	}
+
+	/* Return the number of tests that failed + the number of tests 
+	   that weren't run */
+	r = failures + e_tests - test_count;
+	UNLOCK;
+
+	return r;
+}
+
+/*
+ * Cleanup at the end of the run, produce any final output that might be
+ * required.
+ */
+void
+_cleanup(void)
+{
+
+	LOCK;
+
+	/* If plan_no_plan() wasn't called, and we don't have a plan,
+	   and we're not skipping everything, then something happened
+	   before we could produce any output */
+	if(!no_plan && !have_plan && !skip_all) {
+		diag("Looks like your test died before it could output anything.");
+		UNLOCK;
+		return;
+	}
+
+	if(test_died) {
+		diag("Looks like your test died just after %d.", test_count);
+		UNLOCK;
+		return;
+	}
+
+
+	/* No plan provided, but now we know how many tests were run, and can
+	   print the header at the end */
+	if(!skip_all && (no_plan || !have_plan)) {
+		printf("1..%d\n", test_count);
+	}
+
+	if((have_plan && !no_plan) && e_tests < test_count) {
+		diag("Looks like you planned %d %s but ran %d extra.",
+		     e_tests, e_tests == 1 ? "test" : "tests", test_count - e_tests);
+		UNLOCK;
+		return;
+	}
+
+	if((have_plan || !no_plan) && e_tests > test_count) {
+		diag("Looks like you planned %d %s but only ran %d.",
+		     e_tests, e_tests == 1 ? "test" : "tests", test_count);
+		UNLOCK;
+		return;
+	}
+
+	if(failures)
+		diag("Looks like you failed %d %s of %d.", 
+		     failures, failures == 1 ? "test" : "tests", test_count);
+
+	UNLOCK;
+}
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/src/tap.h
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/src/tap.h	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/src/tap.h	(revision 22254)
@@ -0,0 +1,89 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* '## __VA_ARGS__' is a gcc'ism. C99 doesn't allow the token pasting
+   and requires the caller to add the final comma if they've ommitted
+   the optional arguments */
+#ifdef __GNUC__
+# define ok(e, test, ...) ((e) ?					\
+			   _gen_result(1, __func__, __FILE__, __LINE__,	\
+				       test, ## __VA_ARGS__) :		\
+			   _gen_result(0, __func__, __FILE__, __LINE__,	\
+				       test, ## __VA_ARGS__))
+
+# define ok1(e) ((e) ?							\
+		 _gen_result(1, __func__, __FILE__, __LINE__, "%s", #e) : \
+		 _gen_result(0, __func__, __FILE__, __LINE__, "%s", #e))
+
+# define pass(test, ...) ok(1, test, ## __VA_ARGS__);
+# define fail(test, ...) ok(0, test, ## __VA_ARGS__);
+
+# define skip_start(test, n, fmt, ...)			\
+	do {						\
+		if((test)) {				\
+			skip(n, fmt, ## __VA_ARGS__);	\
+			continue;			\
+		}
+#elif __STDC_VERSION__ >= 199901L /* __GNUC__ */
+# define ok(e, ...) ((e) ?						\
+		     _gen_result(1, __func__, __FILE__, __LINE__,	\
+				 __VA_ARGS__) :				\
+		     _gen_result(0, __func__, __FILE__, __LINE__,	\
+				 __VA_ARGS__))
+
+# define ok1(e) ((e) ?							\
+		 _gen_result(1, __func__, __FILE__, __LINE__, "%s", #e) : \
+		 _gen_result(0, __func__, __FILE__, __LINE__, "%s", #e))
+
+# define pass(...) ok(1, __VA_ARGS__);
+# define fail(...) ok(0, __VA_ARGS__);
+
+# define skip_start(test, n, ...)			\
+	do {						\
+		if((test)) {				\
+			skip(n,  __VA_ARGS__);		\
+			continue;			\
+		}
+#else /* __STDC_VERSION__ */
+# error "Needs gcc or C99 compiler for variadic macros."
+#endif /* __STDC_VERSION__ */
+
+#define skip_end() } while(0);
+
+unsigned int _gen_result(int, const char *, char *, unsigned int, char *, ...);
+
+int plan_no_plan(void);
+int plan_skip_all(char *);
+int plan_tests(unsigned int);
+
+unsigned int diag(char *, ...);
+
+int skip(unsigned int, char *, ...);
+
+void todo_start(char *, ...);
+void todo_end(void);
+
+int exit_status(void);
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/Makefile.am	(revision 22254)
@@ -0,0 +1,7 @@
+SUBDIRS=	diag
+SUBDIRS+=	fail
+SUBDIRS+=	ok
+SUBDIRS+=	pass
+SUBDIRS+=	plan
+SUBDIRS+=	skip
+SUBDIRS+=	todo
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/README
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/README	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/README	(revision 22254)
@@ -0,0 +1,12 @@
+Most of the tests follow the same pattern.
+
+ * test.pl that uses Test::More, and demonstrates whatever functionality 
+   that we're trying to test.  This is the reference code.
+
+ * test.c, which tests the libtap reimplementation of the same functionality.
+
+ * test.t, which compiles the .c program, runs both test scripts, and then 
+   diffs their output to make sure it's identical.
+
+   Right now, test.t is identical in every directory.  This sucks somewhat.
+   It should either be a symlink to a common script
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/diag/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/diag/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/diag/Makefile.am	(revision 22254)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/diag/test.c
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/diag/test.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/diag/test.c	(revision 22254)
@@ -0,0 +1,46 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+	unsigned int rc = 0;
+
+	plan_tests(2);
+
+	rc = diag("A diagnostic message");
+	diag("Returned: %d", rc);
+
+	/* Make sure the failure is passed through */
+	ok(1, "test 1") || diag("ok() failed, and shouldn't");
+	ok(0, "test 2") || diag("ok() passed, and shouldn't");
+
+	return exit_status();
+}
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/diag/test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/diag/test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/diag/test.pl	(revision 22254)
@@ -0,0 +1,16 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+plan tests => 2;
+
+$rc = diag("A diagnostic message");
+diag("Returned: $rc");
+
+ok(1, 'test 1') or diag "ok() failed, and shouldn't";
+ok(0, 'test 2') or diag "ok() passed, and shouldn't";
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/diag/test.t
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/diag/test.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/diag/test.t	(revision 22254)
@@ -0,0 +1,67 @@
+#!/bin/sh
+
+echo '1..7'
+
+perl $srcdir/test.pl 2>/dev/null >test.pl.out
+perlstatus=$?
+
+./test > test.c.out 2>&1
+cstatus=$?
+
+if grep "^# A diagnostic message$" test.c.out > /dev/null ; then
+    echo "ok 1 - found a diagnostic message"
+else
+    echo "not ok 1 - found a diagnostic message"
+    retval=1
+fi
+
+if grep "^# Returned: 0$" test.c.out > /dev/null ; then
+    echo "ok 2 - diag() expected return value" 
+else
+    echo "not ok 2 - diag() expected return value" 
+    retval=1
+fi
+
+if grep "^#     Failed test (.*test.c:main() at line 43)$" test.c.out > /dev/null ; then
+    echo "ok 3 - 'failed test at line' diag" 
+else
+    echo "not ok 3 - 'failed test at line' diag" 
+    retval=1
+fi
+
+if grep "^# ok() passed, and shouldn't$" test.c.out > /dev/null ; then
+    echo "ok 4 - expected diag"
+else
+    echo "ok 4 - expected diag"
+    retval=1
+fi
+
+if grep "^# Looks like you failed 1 test of 2.$" test.c.out > /dev/null ; then
+    echo "ok 5 - failed 1 test"
+ else
+    echo "ok 5 - failed 1 test"
+    retval=1
+fi
+
+sed -e '/^#/D' test.c.out > tmp
+mv tmp test.c.out
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 6 - TAP output is identical'
+else
+	retval=1
+	echo 'not ok 6 - TAP output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 7 - status code'
+else
+	retval=1
+	echo 'not ok 7 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/fail/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/fail/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/fail/Makefile.am	(revision 22254)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/fail/test.c
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/fail/test.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/fail/test.c	(revision 22254)
@@ -0,0 +1,46 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+	unsigned int rc = 0;
+
+	rc = plan_tests(2);
+	diag("Returned: %d", rc);
+
+	rc = fail("test to fail");
+	diag("Returned: %d", rc);
+
+	rc = fail("test to fail %s", "with extra string");
+	diag("Returned: %d", rc);
+
+	return exit_status();
+}
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/fail/test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/fail/test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/fail/test.pl	(revision 22254)
@@ -0,0 +1,17 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 2;
+diag("Returned: " . sprintf('%d', $rc));
+
+$rc = fail('test to fail');
+diag("Returned: $rc");
+
+$rc = fail('test to fail with extra string');
+diag("Returned: $rc");
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/fail/test.t
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/fail/test.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/fail/test.t	(revision 22254)
@@ -0,0 +1,74 @@
+#!/bin/sh
+
+echo '1..8'
+
+perl $srcdir/test.pl 2>/dev/null > test.pl.out
+perlstatus=$?
+
+./test > test.c.out 2>&1 
+cstatus=$?
+
+if grep "^# Returned: 2$" test.c.out >/dev/null ; then
+  echo "ok 1 - expected return value";
+else
+  echo "not ok 1 - expected return value";
+  retval=1
+fi
+
+if grep "^#     Failed test (.*test.c:main() at line 39)$" test.c.out >/dev/null ; then
+  echo "ok 2 - failed expected test";
+else
+  echo "not ok 2 - failed expected test";
+  retval=1
+fi
+
+if grep "^# Returned: 0$" test.c.out >/dev/null ; then
+  echo "ok 3 - expected return value";
+else
+  echo "not ok 3 - expected return value";
+  retval=1
+fi
+
+if grep "^#     Failed test (.*test.c:main() at line 42)$" test.c.out >/dev/null ; then
+  echo "ok 4 - failed expected test";
+else
+  echo "not ok 4 - failed expected test";
+  retval=1
+fi
+  
+if grep "^# Returned: 0$" test.c.out >/dev/null ; then
+  echo "ok 5 - expected return value";
+else
+  echo "not ok 5 - expected return value";
+  retval=1
+fi
+
+if grep "^# Looks like you failed 2 tests of 2.$" test.c.out >/dev/null ; then
+  echo "ok 6 - expected return value";
+else
+  echo "not ok 6 - expected return value";
+  retval=1
+fi
+
+sed -e '/^#/D' test.c.out > tmp
+mv tmp test.c.out
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 7 - output is identical'
+else
+	retval=1
+	echo 'not ok 7 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 8 - status code'
+else
+	retval=1
+	echo 'not ok 8 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/Makefile.am	(revision 22254)
@@ -0,0 +1,3 @@
+SUBDIRS  =	ok
+SUBDIRS +=	ok-hash
+SUBDIRS +=	ok-numeric
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok-hash/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok-hash/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok-hash/Makefile.am	(revision 22254)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok-hash/test.c
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok-hash/test.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok-hash/test.c	(revision 22254)
@@ -0,0 +1,52 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+	unsigned int rc = 0;
+
+	rc = plan_tests(4);
+	diag("Returned: %d", rc);
+
+	rc = ok(1, "Test with no hash");
+	diag("Returned: %d", rc);
+
+	rc = ok(1, "Test with one # hash");
+	diag("Returned: %d", rc);
+
+        rc = ok(1, "Test with # two # hashes");
+	diag("Returned: %d", rc);
+
+	rc = ok(1, "Test with ## back to back hashes");
+	diag("Returned: %d", rc);
+	
+	return exit_status();
+}
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok-hash/test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok-hash/test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok-hash/test.pl	(revision 22254)
@@ -0,0 +1,24 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 4;
+diag("Returned: " . sprintf("%d", $rc));
+
+
+$rc = ok(1, 'Test with no hash');
+diag("Returned: $rc");
+
+$rc = ok(1, 'Test with one # hash');
+diag("Returned: $rc");
+
+$rc = ok(1, 'Test with # two # hashes');
+diag("Returned: $rc");
+
+$rc = ok(1, 'Test with ## back to back hashes');
+diag("Returned: $rc");
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok-hash/test.t
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok-hash/test.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok-hash/test.t	(revision 22254)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok-numeric/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok-numeric/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok-numeric/Makefile.am	(revision 22254)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok-numeric/test.c
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok-numeric/test.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok-numeric/test.c	(revision 22254)
@@ -0,0 +1,49 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+	unsigned int rc = 0;
+
+	rc = plan_tests(3);
+	diag("Returned: %d", rc);
+
+	rc = ok(1, "First test");
+	diag("Returned: %d", rc);
+
+	rc = ok(1, "1");
+	diag("Returned: %d", rc);
+
+	rc = ok(1, "Third test");
+	diag("Returned: %d", rc);
+
+	return exit_status();
+}
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok-numeric/test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok-numeric/test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok-numeric/test.pl	(revision 22254)
@@ -0,0 +1,21 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 3;
+diag("Returned: " . sprintf("%d", $rc));
+
+
+$rc = ok(1, 'First test');
+diag("Returned: $rc");
+
+$rc = ok(1, '1');
+diag("Returned: $rc");
+
+$rc = ok(1, 'Third test');
+diag("Returned: $rc");
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok-numeric/test.t
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok-numeric/test.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok-numeric/test.t	(revision 22254)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok/Makefile.am	(revision 22254)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok/test.c
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok/test.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok/test.c	(revision 22254)
@@ -0,0 +1,55 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+	unsigned int rc = 0;
+
+	rc = plan_tests(5);
+	diag("Returned: %d", rc);
+
+	rc = ok(1 == 1, "1 equals 1");
+	diag("Returned: %d", rc);
+
+	rc = ok(1 == 1, "1 equals %d", 1);
+	diag("Returned: %d", rc);
+
+	rc = ok1(1 == 1);
+	diag("Returned: %d", rc);
+
+	rc = ok(1 == 2, "1 equals 2");
+	diag("Returned: %d", rc);
+
+	rc = ok1(1 == 2);
+	diag("Returned: %d", rc);
+
+	return exit_status();
+}
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok/test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok/test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok/test.pl	(revision 22254)
@@ -0,0 +1,27 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 5;
+diag("Returned: " . sprintf("%d", $rc));
+
+
+$rc = ok(1 == 1, '1 equals 1');	# Test ok() passes when it should
+diag("Returned: $rc");
+
+$rc = ok(1 == 1, '1 equals 1'); # Used for %d testing in test.c
+diag("Returned: $rc");
+
+$rc = ok(1 == 1, '1 == 1');	# Test ok1() passes when it should
+diag("Returned: $rc");
+
+$rc = ok(1 == 2, '1 equals 2');	# Test ok() fails when it should
+diag("Returned: $rc");
+
+$rc = ok(1 == 2, '1 == 2');	# Test ok1() fails when it should
+diag("Returned: $rc");
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok/test.t
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok/test.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/ok/ok/test.t	(revision 22254)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/pass/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/pass/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/pass/Makefile.am	(revision 22254)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/pass/test.c
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/pass/test.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/pass/test.c	(revision 22254)
@@ -0,0 +1,46 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+	unsigned int rc = 0;
+
+	rc = plan_tests(2);
+	diag("Returned: %d", rc);
+
+	rc = pass("test to pass");
+	diag("Returned: %d", rc);
+
+	rc = pass("test to pass %s", "with extra string");
+	diag("Returned: %d", rc);
+
+	return exit_status();
+}
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/pass/test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/pass/test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/pass/test.pl	(revision 22254)
@@ -0,0 +1,17 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 2;
+diag("Returned: " . sprintf('%d', $rc));
+
+$rc = pass('test to pass');
+diag("Returned: $rc");
+
+$rc = pass('test to pass with extra string');
+diag("Returned: $rc");
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/pass/test.t
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/pass/test.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/pass/test.t	(revision 22254)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/Makefile.am	(revision 22254)
@@ -0,0 +1,7 @@
+SUBDIRS  =	no-tests
+SUBDIRS +=	no_plan
+SUBDIRS +=	not-enough-tests
+SUBDIRS +=	too-many-plans
+SUBDIRS +=	too-many-tests
+SUBDIRS +=	sane
+SUBDIRS +=	skip_all
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/no-tests/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/no-tests/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/no-tests/Makefile.am	(revision 22254)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/no-tests/test.c
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/no-tests/test.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/no-tests/test.c	(revision 22254)
@@ -0,0 +1,43 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+	unsigned int rc = 0;
+
+	rc = plan_tests(0);
+	diag("Returned: %d", rc);
+
+	rc = ok(1, NULL);
+	diag("Returned: %d", rc);
+
+	return exit_status();
+}
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/no-tests/test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/no-tests/test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/no-tests/test.pl	(revision 22254)
@@ -0,0 +1,14 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 0;
+diag("Returned: " . sprintf("%d", $rc));
+
+$rc = ok(1);
+diag("Returned: $rc");
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/no-tests/test.t
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/no-tests/test.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/no-tests/test.t	(revision 22254)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/no_plan/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/no_plan/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/no_plan/Makefile.am	(revision 22254)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/no_plan/test.c
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/no_plan/test.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/no_plan/test.c	(revision 22254)
@@ -0,0 +1,43 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+	unsigned int rc = 0;
+
+	rc = plan_no_plan();
+	diag("Returned: %d", rc);
+
+	rc = ok(1, NULL);
+	diag("Returned: %d", rc);
+
+	return exit_status();
+}
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/no_plan/test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/no_plan/test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/no_plan/test.pl	(revision 22254)
@@ -0,0 +1,14 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+my $rc = 0;
+
+use Test::More;
+
+$rc = plan qw(no_plan);
+diag("Returned: " . sprintf("%d", $rc));
+
+$rc = ok(1);
+diag("Returned: $rc");
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/no_plan/test.t
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/no_plan/test.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/no_plan/test.t	(revision 22254)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/not-enough-tests/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/not-enough-tests/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/not-enough-tests/Makefile.am	(revision 22254)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/not-enough-tests/test.c
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/not-enough-tests/test.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/not-enough-tests/test.c	(revision 22254)
@@ -0,0 +1,49 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+	unsigned int rc = 0;
+
+	rc = plan_tests(1);
+	diag("Returned: %d", rc);
+
+	rc = ok(1, NULL);
+	diag("Returned: %d", rc);
+
+	rc = ok(1, NULL);
+	diag("Returned: %d", rc);
+
+	rc = ok(1, NULL);
+	diag("Returned: %d", rc);
+
+	return exit_status();
+}
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/not-enough-tests/test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/not-enough-tests/test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/not-enough-tests/test.pl	(revision 22254)
@@ -0,0 +1,20 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 1;
+diag("Returned: " . sprintf("%d", $rc));
+
+$rc = ok(1);
+diag("Returned: $rc");
+
+$rc = ok(1);
+diag("Returned: $rc");
+
+$rc = ok(1);
+diag("Returned: $rc");
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/not-enough-tests/test.t
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/not-enough-tests/test.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/not-enough-tests/test.t	(revision 22254)
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	# comment this out until we're exit-code compatible with Test::More
+	#retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/sane/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/sane/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/sane/Makefile.am	(revision 22254)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/sane/test.c
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/sane/test.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/sane/test.c	(revision 22254)
@@ -0,0 +1,43 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+	unsigned int rc = 0;
+
+	rc = plan_tests(1);
+	diag("Returned: %d", rc);
+
+	rc = ok(1, NULL);
+	diag("Returned: %d", rc);
+
+	return exit_status();
+}
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/sane/test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/sane/test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/sane/test.pl	(revision 22254)
@@ -0,0 +1,14 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 1;
+diag("Returned: " . sprintf("%d", $rc));
+
+$rc = ok(1);
+diag("Returned: $rc");
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/sane/test.t
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/sane/test.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/sane/test.t	(revision 22254)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/skip_all/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/skip_all/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/skip_all/Makefile.am	(revision 22254)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/skip_all/plan.c
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/skip_all/plan.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/skip_all/plan.c	(revision 22254)
@@ -0,0 +1,39 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "tap.h"
+
+/* Run pre-defined tests on the test library to make sure that the basic
+   functionality works, and it can be used to test itself afterwards */
+
+int
+main(int argc, char *argv[])
+{
+	plan_skip_all("No good reason");
+}
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/skip_all/test.c
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/skip_all/test.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/skip_all/test.c	(revision 22254)
@@ -0,0 +1,38 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+	unsigned int rc = 0;
+
+	rc = plan_skip_all("No good reason");
+	diag("Returned: %d", rc);
+
+	return exit_status();
+}
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/skip_all/test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/skip_all/test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/skip_all/test.pl	(revision 22254)
@@ -0,0 +1,11 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan skip_all => "No good reason";
+diag("Returned: " . sprintf("%d", $rc));
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/skip_all/test.t
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/skip_all/test.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/skip_all/test.t	(revision 22254)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/too-many-plans/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/too-many-plans/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/too-many-plans/Makefile.am	(revision 22254)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/too-many-plans/test.c
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/too-many-plans/test.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/too-many-plans/test.c	(revision 22254)
@@ -0,0 +1,49 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+	unsigned int rc = 0;
+
+	rc = plan_tests(1);
+	diag("Returned: %d", rc);
+
+	rc = ok(1, NULL);
+	diag("Returned: %d", rc);
+
+	rc = plan_tests(1);
+	diag("Returned: %d", rc);
+
+	rc = ok(0, NULL);
+	diag("Returned: %d", rc);
+
+	return exit_status();
+}
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/too-many-plans/test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/too-many-plans/test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/too-many-plans/test.pl	(revision 22254)
@@ -0,0 +1,20 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 1;
+diag("Returned: " . sprintf("%d", $rc));
+
+$rc = ok(1);
+diag("Returned: $rc");
+
+$rc = plan tests => 1;
+diag("Returned: $rc");
+
+$rc = ok(0);
+diag("Returned: $rc");
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/too-many-plans/test.t
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/too-many-plans/test.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/too-many-plans/test.t	(revision 22254)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/too-many-tests/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/too-many-tests/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/too-many-tests/Makefile.am	(revision 22254)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/too-many-tests/test.c
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/too-many-tests/test.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/too-many-tests/test.c	(revision 22254)
@@ -0,0 +1,46 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+	unsigned int rc = 0;
+
+	rc = plan_tests(5);
+	diag("Returned: %d", rc);
+
+	rc = ok(1, NULL);
+	diag("Returned: %d", rc);
+
+	rc = ok(0, NULL);
+	diag("Returned: %d", rc);
+
+	return exit_status();
+}
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/too-many-tests/test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/too-many-tests/test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/too-many-tests/test.pl	(revision 22254)
@@ -0,0 +1,17 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 5;
+diag("Returned: " . sprintf("%d", $rc));
+
+$rc = ok(1);
+diag("Returned: $rc");
+
+$rc = ok(0);
+diag("Returned: $rc");
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/too-many-tests/test.t
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/too-many-tests/test.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/plan/too-many-tests/test.t	(revision 22254)
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+    # we're not exit-status compatible with Test::More yet
+	#retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/skip/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/skip/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/skip/Makefile.am	(revision 22254)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/skip/test.c
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/skip/test.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/skip/test.c	(revision 22254)
@@ -0,0 +1,69 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+	unsigned int rc = 0;
+	unsigned int side_effect = 0;
+
+	rc = plan_tests(4);
+	diag("Returned: %d", rc);
+
+	rc = ok(1 == 1, "1 equals 1");	/* Should always work */
+	diag("Returned: %d", rc);
+
+	do {
+		if(1) {
+			rc = skip(1, "Testing skipping");
+			continue;
+		}
+		
+		side_effect++;
+
+		ok(side_effect == 1, "side_effect checked out");
+
+	} while(0);
+
+	diag("Returned: %d", rc);
+
+	skip_start(1 == 1, 1, "Testing skipping #2");
+
+	side_effect++;
+	rc = ok(side_effect == 1, "side_effect checked out");
+	diag("Returned: %d", rc);
+
+	skip_end();
+
+	rc = ok(side_effect == 0, "side_effect is %d", side_effect);
+	diag("Returned: %d", rc);
+
+	return exit_status();
+}
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/skip/test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/skip/test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/skip/test.pl	(revision 22254)
@@ -0,0 +1,40 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 4;
+diag("Returned: " . sprintf("%d", $rc));
+
+my $side_effect = 0;		# Check whether skipping has side effects
+
+$rc = ok(1 == 1, '1 equals 1');	# Test ok() passes when it should
+diag("Returned: $rc");
+
+# Start skipping
+SKIP: {
+	$rc = skip "Testing skipping", 1;
+
+	$side_effect++;
+
+	$rc = ok($side_effect == 1, '$side_effect checked out');
+}
+
+diag("Returned: $rc");
+
+SKIP: {
+	$rc = skip "Testing skipping #2", 1;
+	diag("Returned: $rc");
+
+	$side_effect++;
+
+	$rc = ok($side_effect == 1, '$side_effect checked out');
+	diag("Returned: $rc");
+}
+
+$rc = ok($side_effect == 0, "side_effect is $side_effect");
+diag("Returned: $rc");
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/skip/test.t
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/skip/test.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/skip/test.t	(revision 22254)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/todo/Makefile.am
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/todo/Makefile.am	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/todo/Makefile.am	(revision 22254)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/todo/test.c
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/todo/test.c	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/todo/test.c	(revision 22254)
@@ -0,0 +1,68 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+	unsigned int rc = 0;
+	unsigned int side_effect = 0;
+
+	rc = plan_tests(5);
+	diag("Returned: %d", rc);
+
+	rc = ok(1 == 1, "1 equals 1");	/* Should always work */
+	diag("Returned: %d", rc);
+
+	todo_start("For testing purposes");
+
+	side_effect++;
+
+	/* This test should fail */
+	rc = ok(side_effect == 0, "side_effect checked out");
+	diag("Returned: %d", rc);
+
+	/* This test should unexpectedly succeed */
+	rc = ok(side_effect == 1, "side_effect checked out");
+	diag("Returned: %d", rc);
+
+	todo_end();
+
+	todo_start("Testing printf() %s in todo_start()", "expansion");
+
+	rc = ok(0, "dummy test");
+	diag("Returned: %d", rc);
+
+	todo_end();
+
+	rc = ok(side_effect == 1, "side_effect is %d", side_effect);
+	diag("Returned: %d", rc);
+
+	return exit_status();
+}
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/todo/test.pl
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/todo/test.pl	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/todo/test.pl	(revision 22254)
@@ -0,0 +1,41 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 5;
+diag("Returned: " . sprintf("%d", $rc));
+
+my $side_effect = 0;		# Check whether TODO has side effects
+
+$rc = ok(1 == 1, '1 equals 1');	# Test ok() passes when it should
+diag("Returned: $rc");
+
+# Start TODO tests
+TODO: {
+	local $TODO = 'For testing purposes';
+
+	$side_effect++;
+
+	# This test should fail
+	$rc = ok($side_effect == 0, 'side_effect checked out');
+	diag("Returned: $rc");
+
+	# This test should unexpectedly succeed
+	$rc = ok($side_effect == 1, 'side_effect checked out');
+	diag("Returned: $rc");
+}
+
+TODO: {
+	local $TODO = 'Testing printf() expansion in todo_start()';
+
+	$rc = ok(0, 'dummy test');
+	diag("Returned: $rc");
+}
+
+$rc = ok($side_effect == 1, "side_effect is $side_effect");
+diag("Returned: $rc");
Index: /branches/jh_branches/jhoblitt/psLib/test/tap/tests/todo/test.t
===================================================================
--- /branches/jh_branches/jhoblitt/psLib/test/tap/tests/todo/test.t	(revision 22254)
+++ /branches/jh_branches/jhoblitt/psLib/test/tap/tests/todo/test.t	(revision 22254)
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+# Test:;More prints diagnostic from TODO tests on stdout
+# http://rt.cpan.org/Ticket/Display.html?id=14982
+sed '/^#/D' test.pl.out > tmp
+mv tmp test.pl.out
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
