root/honeyclient/tags/exp/UP2-kindlund-dynamic_updates/lib/HoneyClient/DB.pm

Revision 796, 33.9 kB (checked in by kindlund, 1 year ago)

Version bump.

  • Property svn:keywords set to Id "$file"
Line 
1 #######################################################################
2 # Created on:  February 17, 2007
3 # Package:     HoneyClient::DB
4 # File:        DB.pm
5 # Description: Abstract class for controlling storage of HoneyClient
6 #              data into a database.
7 #
8 # CVS: $Id$
9 #
10 # @author mbriggs, kindlund
11 #
12 # Copyright (C) 2007 The MITRE Corporation.  All rights reserved.
13 #
14 # This program is free software; you can redistribute it and/or
15 # modify it under the terms of the GNU General Public License
16 # as published by the Free Software Foundation, using version 2
17 # of the License.
18 #
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 # GNU General Public License for more details.
23 #
24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
27 # 02110-1301, USA.
28 #
29 #######################################################################
30
31 =pod
32
33 =head1 NAME
34
35 HoneyClient::DB - Perl extension to provide an abstract interface for
36 storing HoneyClient data into a database.
37
38 =head1 VERSION
39
40 This documentation refers to HoneyClient::DB version 0.99.
41
42 =head1 SYNOPSIS
43
44 As a generic example, let's store data about superheroes.
45
46 =head2 DEFINE SCHEMAS
47
48   # First, we define a schema for each superhero ability (child object).
49   use HoneyClient::DB;
50   package HoneyClient::DB::SuperHero::Ability;
51   use base("HoneyClient::DB");
52
53   # Define Ability Schema
54   our %fields = (
55       string => {
56           # Each ability should have a name.
57           name => {
58               # This name should be required.
59               required => 1, # Must exist and is not null
60           },
61       },
62
63       # Each ability may have an optional description.
64       text => [ 'description' ],
65
66       # Each ability may have an optional recharge time.
67       int  => [ 'recharge_time' ],
68   );
69  
70   # Next, we define a schema for each superhero (parent object).
71   package HoneyClient::DB::SuperHero;
72   use base("HoneyClient::DB");
73  
74   # Define SuperHero Schema
75   our %fields = (
76       string => {
77           # Each superhero should have a name.
78           name => {
79               # This name should be required.
80               required => 1,
81               key => $HoneyClient::DB::KEY_UNIQUE_MULT,
82           },
83           # Each superhero may have an optional real name.
84           real_name => {
85               key => $HoneyClient::DB::KEY_UNIQUE_MULT,
86           },
87           # If 2 SuperHero Objects have the same 'name' and 'real_name'
88           # fields, then only the first object will be inserted succesfully
89       },
90      
91       # Each superhero may have optional height and weight stats.
92       int => [ 'height', 'weight' ],
93
94       # Each superhero must have a primary ability.
95       ref => {
96           primary_ability => {
97               # Reference child object type.
98               objclass=> "HoneyClient::DB::SuperHero::Ability",
99
100               # This should be required.
101               required => 1,
102           },
103       },
104
105       # Each superhero may have optional abilities.
106       array => {
107           abilities => {
108               # Reference child object type.
109               objclass=> "HoneyClient::DB::SuperHero::Ability",
110           },
111       },
112
113       # Each superhero should have a birth date.
114       timestamp => {
115           birth_date => {
116               required => 1,
117           },
118       },
119   );
120  
121   1;
122
123 =head2 USE SCHEMAS
124
125   # Now, we start generating data to insert into our database.
126
127   use HoneyClient::DB::SuperHero;
128   use Data::Dumper;
129
130   # Create a new superhero.
131   my $hero = {
132       name       => 'Superman',
133       real_name  => 'Clark Kent',
134       weight     => 225,
135       height     => 75,
136       birth_date => '1998-06-01 12:34:56', # YYYY-MM-DD HH:MM:SS
137       primary_ability => {
138           name              => 'Super Strength',
139           description       => 'More powerful than a locomotive.',
140       },
141       abilities  => [
142           {
143               name          => 'Flight',
144               description   => "It's a bird, it's a plane.",
145           },
146           {
147               name          => 'Heat Vision',
148               recharge_time => 5, # in seconds
149           },
150       ],
151   };
152    
153   # Instantiate a new SuperHero object.
154   # Upon creation, the data will be checked against the schema.
155   # This call will croak if any errors occur.
156   $hero = HoneyClient::DB::SuperHero->new($hero);
157
158   # Insert the data into the database.
159   $hero->insert();
160  
161   # Retrieve the superhero.
162   my $filter = {
163       name => 'Superman',
164   };
165    
166   # Retrieves rows in the SuperHero table where name is 'Superman'.
167   # NOTE: At this time, the returned data is NOT identical to
168   # the object inserted.
169   my $inserted_hero = HoneyClient::DB::SuperHero->select($filter);
170
171   # Printing the contents of the returned content should clarify
172   # how the data looks.
173   $Data::Dumper::Indent = 1;
174   $Data::Dumper::Terse = 0;
175   print Dumper($inserted_hero) . "\n";
176
177 =head1 DESCRIPTION
178
179 This library is an abstract class used to access and store HoneyClient
180 within a database. The class is not to be used directly, but can be inherited
181 by sub-classes, in order to indirectly store specific types of data into a
182 database.
183
184 B<Note>: Any calls made to this library will fail, if a database is not properly
185 described in the <HoneyClient/><DB/> section of the etc/honeyclient.xml
186 configuration file or if the library cannot establish a connection to the
187 database.
188
189 =head2 SCHEMA DEFINITION
190
191 The schema for a HoneyClient::DB subclass is created from the B<%fields>
192 variable, a multi-level hash table.
193
194 =head3 FIRST LEVEL: DATA TYPE
195
196 The keys at the first level of B<%fields> define the data types to be used
197 for each column, which are named as keys in the second level.  The following
198 is a list of acceptable data types:
199
200 =over 4
201
202 =item * B<'int'>
203
204 An integer.
205
206 =item * B<'string'>
207
208 A string no longer than 255 characters.
209
210 =item * B<'text'>
211
212 A string no longer than 65,535 characters.
213
214 =item * B<'timestamp'>
215
216 An ISO8601 compliant timestamp (i.e., 'MMDDYYYY HH:MM:SS').
217
218 =item * B<'array'>
219
220 An array.  Used to represent one-to-many relationships.
221
222 B<Note>: If this type is specified, then the L<'objclass'> option
223 must be set within each column name.
224
225 =item * B<'ref'>
226
227 A reference.  Used to represent one-to-one relationships.
228
229 B<Note>: If this type is specified, then the L<'objclass'> option
230 must be set within each column name.
231
232 =back
233
234 =head3 SECOND LEVEL: COLUMN NAMES
235
236 Column names are defined as keys in the second level of B<%fields>.  If
237 each column does not need any special options (e.g., making the column
238 required), then an array reference can hold all the column names.
239 For example, the following schema defines 3 default integer fields:
240
241   %our fields = {
242       int => [
243           'col_a',
244           'col_b',
245           'col_c',
246       ],
247   };
248
249 However, if some of the columns need special options set (e.g., making 'col_b'
250 required), then a sub-hash table should be defined instead, as follows:
251
252   %our fields = {
253       int => {
254           'col_a' => {},
255           'col_b' => {
256               'required' => 1,
257           },
258           'col_c' => {},
259       },
260   };
261
262 =head3 THIRD LEVEL: OPTIONS
263
264 If needed, options are defined in the third level of B<%fields>.  These
265 options are described as follows:
266
267 =over 4
268
269 =item * B<'check_func'>
270
271 If defined, then this contains a reference to a subroutine that will
272 verify the actual column data is in a proper format.  This overrides the
273 default internal check function for the data type of that column.
274
275 =item * B<'init_val'>
276
277 If defined, then this value will be the default value that the database
278 will assign to the column, if empty data is inserted into this column.
279
280 =item * B<'key'>
281
282 If defined, then this value creates an index for the column.
283 Possible values are:
284
285 =over 4
286
287 =item * B<$HoneyClient::DB::KEY_INDEX>
288
289 If set, an index will be created in the database to improve the search
290 time of this column.  This option is only recommended for very frequently
291 searched columns.
292
293 =item * B<$HoneyClient:DB::KEY_UNIQUE>
294
295 If set, a UNIQUE index is created for the column. If a record is inserted that
296 has a match with this column in a previously existing record, the insert will
297 fail on the database side, but the 'id' of that existing record will be
298 returned.
299
300 =item * B<$HoneyClient:DB::KEY_UNIQUE_MULT>
301
302 If set, the column is added to a UNIQUE index comprised of all other columns
303 with the KEY_UNIQUE_MULT key option. This index is used to ensure ALL VALUES for
304 the columns in the index are distinct. An insert of a record matching an
305 existing record will return the ID of that record.
306
307 =back
308
309 =item * B<'objclass'>
310
311 This option is required and only used by the L<'array'> and L<'ref'>
312 data types.  The value should be a string which contains the package name of
313 the schema to include as a child.
314
315 =item * B<'required'>
316
317 If defined and set to 1, then this option will cause all subsequent
318 B<HoneyClient::DB::*-E<gt>L<new>($data)> operations to fail, if the B<$data>
319 does not contain the required field.
320
321 =back
322
323 =head1 DATABASE CONFIGURATION
324
325 This library expects to connect to a MySQL v5.0 or greater database.
326 To specify which database this library should use, see the
327 <HoneyClient/><DB/> section of the etc/honeyclient.xml configuration file
328 for further details.
329
330 =cut
331
332 package HoneyClient::DB;
333
334 use Data::Dumper qw(Dumper);
335 use strict 'vars', 'subs';
336 use warnings;
337
338 BEGIN {
339
340     #Dependencies
341     use DBI;
342     use Carp ();
343     use HoneyClient::Util::Config;
344     use DateTime::Format::ISO8601;
345     use Math::BigInt;
346     use Log::Log4perl qw(:easy);
347
348     require Exporter;
349
350     # Traps signals, allowing END: blocks to perform cleanup.
351     use sigtrap qw(die untrapped normal-signals error-signals);
352     $SIG{PIPE} = 'IGNORE';    # Do not exit on broken pipes.
353
354     #Globals
355     our @ISA    = qw(Exporter);
356     our @EXPORT = qw();
357     our @EXPORT_OK;
358     our $VERSION = 0.99;
359
360     my $database_version;     #  = $dbh->get_info(  18 ); # SQL_DBMS_VER
361 }
362
363 # The global logging object.
364 our $LOG = get_logger();
365
366 our $dbhandle;
367
368 # To be used ONLY INTERNALLY!
369 our ( %_types, %_check, %_required, %_init_val, %_keys, %defaults );
370
371 # %fields must be defined by all children classes
372 our %fields;
373
374 #constants
375 our ( $STATUS_DELETED, $STATUS_ADDED, $STATUS_MODIFIED ) =
376   ( 0, 1, 2 );    # Integrity status field
377 our ( $KEY_INDEX, $KEY_UNIQUE, $KEY_UNIQUE_MULT ) =
378   ( 0, 1, 2 );    # Uniqueness of Attributes
379 our $debug = 0;
380
381 # Initialize Connection
382 our %config;
383 $config{driver} = "mysql";
384 $config{host}   = getVar( name => "host", namespace => "HoneyClient::DB" );
385 $config{port}   = getVar( name => "port", namespace => "HoneyClient::DB" );
386 $config{user}   = getVar( name => "user", namespace => "HoneyClient::DB" );
387 $config{pass}   = getVar( name => "pass", namespace => "HoneyClient::DB" );
388 $config{dbname} = getVar( name => "dbname", namespace => "HoneyClient::DB" );
389
390 if ( !db_exists(%config) ) {
391     die;
392 }
393
394 END {
395     $dbhandle->disconnect() if $dbhandle;
396 }
397
398 =pod
399
400 =head1 METHODS
401
402 =head2 Object Creation
403
404 =over 4
405
406 =item new
407
408 Receives an unblessed hash, imports the schema (if necessary), checks that
409 required fields contain the proper data, and returns the blessed object.
410
411 It must be called using an object class derived from HoneyClient::DB.
412 For Example:
413
414   $my_obj = new HoneyClient::DB::SomeObj->new({
415           field_a => "foo",
416           field_b => "bar"
417   });
418
419 =cut
420
421 sub new {
422     my ( $class, $self ) = @_;
423
424     bless( $self, $class );
425
426     # Check if Schema has been imported
427     _import_schema($class) if ( !exists( $_types{$class} ) );
428
429     # Make sure required Attributes are set. Fail if not.
430     my @missing = $self->_check_required();
431     if ( scalar @missing ) {
432         $LOG->fatal( "Object missing required attribute(s): "
433               . join( ', ', @missing )
434               . '.' );
435         Carp::croak( "Object missing required attribute(s): "
436               . join( ', ', @missing )
437               . '.\n' );
438     }
439
440     # Check if ref and array objects have been initialized. If not call new
441     foreach my $key ( keys %$self ) {
442         eval {
443             if ( $self->{$key} )
444             {
445                 $self->{$key} = $_check{$class}{$key}->( $self->{$key} );
446             }
447         };
448         if ($@) {
449             $LOG->fatal("Invalid Object $key\t$@");
450             Carp::croak "Invalid Object $key\n\t$@";
451         }
452         if ( $_types{$class}{$key} =~ m/(array|ref):(.*)/ ) {
453             my $ref        = ref( $self->{$key} );
454             my $childType  = $1;
455             my $childClass = $2;
456             if ( $childClass->can('new') ) {
457                 if ( $ref eq 'HASH' and $childType eq 'ref' ) {
458                     $self->{$key} = $childClass->new( $self->{$key} );
459                 }
460                 if ( $ref eq 'ARRAY' and $childType eq 'array' ) {
461                     foreach my $obj ( @{ $self->{$key} } ) {
462                         $obj = $childClass->new($obj);
463                     }
464                 }
465             }
466             else {
467                 $LOG->fatal("Invalid Object! $childType does not exist");
468                 Carp::croak "Invalid Object! $childType does not exist";
469             }
470         }
471     }
472     return $self;
473 }
474
475 ################# Initialization Helper Functions #################
476
477 sub _check_required {
478     my $self  = shift;
479     my $class = ref $self;
480
481     # make sure field is not undef if 'required' option is set
482     if ( exists $_required{$class} ) {
483         my @missing;
484         foreach ( keys %{ $_required{$class} } ) {
485             push( @missing, $_ )
486               if ( !defined( $self->{$_} ) or ( $self->{$_} eq "" ) );
487         }
488         return @missing;
489     }
490     return;
491 }
492
493 sub _import_schema {
494     my $class  = shift;
495     my $schema = \%{ $class . "::fields" };
496
497     # Parase Attributes; store types and options.
498     while ( my ( $type, $attrib ) = each(%$schema) ) {
499         my $ref = ref $attrib;
500
501         # Attributes in array format use default options
502         if ( $ref eq 'ARRAY' ) {
503             foreach ( @{$attrib} ) {
504                 $_types{$class}{$_} = $type;
505                 if ( $type =~ m/(ref|array)/ ) {
506                     delete $_types{$class};
507                     $LOG->fatal("Invalid Object Type. ref AND array types must "
508                       . "be defined as a hash containing 'objclass'");
509                     Carp::croak "Invalid Object Type. ref AND array types must "
510                       . "be defined as a hash containing 'objclass'";
511                 }
512                 $_check{$class}{$_} = $defaults{$type}{check_func}
513                   or $_check{$class}{$_} = \&check_nothing;
514             }
515         }
516
517         # Parse options for attributes in hash table format
518         elsif ( $ref eq 'HASH' ) {
519             while ( my ( $a, $opts ) = each %$attrib ) {
520                 $_types{$class}{$a} = $type;
521                 if ( $opts->{required} ) {
522                     $_required{$class}{$a} = 1;
523                 }
524
525                 # array and ref types require the objclass option
526                 if ( $type =~ m/^(array|ref)$/ ) {
527                     if ( !exists $opts->{objclass} ) {
528                         $LOG->fatal("$1 of unknown class: $a");
529                         Carp::croak "$1 of unknown class: $a";
530                     }
531                     if ( !exists $_types{ $opts->{objclass} } ) {
532                         _import_schema( $opts->{objclass} );
533                     }
534                     $_types{$class}{$a} .= ':' . $opts->{objclass};
535                 }
536
537                 # Check function will ensure data is of proper format
538                 if ( $opts->{check_func} ) {
539                     $_check{$class}{$a} = $opts->{check_func};
540                 }
541                 else {
542                     $_check{$class}{$a} = $defaults{$type}{check_func}
543                       or $_check{$class}{$a} = \&check_nothing;
544                 }
545
546                 # key option determines if attribute is an index
547                 if ( $opts->{key} ) {
548                     $_keys{$class}{$a} = $opts->{key};
549                 }
550                 if ( $opts->{init_val} ) {
551                     $_init_val{$class}{$a} = $opts->{key};
552                 }
553             }
554         }
555         else {
556             $LOG->warn("$class\{$type\} is defined improperly");
557         }
558     }
559
560     # Add the table to the DB if necessary
561     # TODO: Move to install script??
562     if (!$class->deploy_table()) {
563         $LOG->fatal("${class}->_import_schema: " . "Failed to deploy table");
564         Carp::croak("${class}->_import_schema: " . "Failed to deploy table");
565     }
566 }
567
568 =back
569
570 =head2 Database Operations
571
572 =over 4
573
574 =item insert
575
576 Creates and executes a SQL INSERT statement for the referenced object. The
577 object must be initialized at the time this method is called.
578
579   $my_obj->insert();
580
581 B<Input>
582
583 There are no parameters, however the calling object is used as input for the
584 insert operation.
585
586 B<Return Value>
587
588 Returns the 'id' of the (parent) object inserted.
589
590 =cut
591
592 sub insert {
593     my $obj = shift;
594     my $id  = undef;
595
596     $dbhandle = HoneyClient::DB::_connect(%config);
597
598     # Attempt insert; commit if succeeds, else rollback
599     $LOG->debug("Attempting insert operation.");
600     eval { $id = _insert( $obj, undef ); };
601     if ($@) {
602         $LOG->warn("insert failed, Rolling Back: $@");
603         $dbhandle->rollback();
604     }
605     else {
606         $dbhandle->commit();
607     }
608     $dbhandle->disconnect() if $dbhandle;
609     return $id;
610 }
611
612 ##################### Insert Helper Functions #####################
613
614 sub _insert {
615     my ( $obj, $fk_col, $fk_id ) = @_;
616     my $ref = ref $obj;
617
618     if ( $ref eq 'ARRAY' ) {
619         return _insert_array( $obj, $fk_col, $fk_id );
620     }
621     elsif ( exists $_types{$ref} ) {
622         return _insert_obj( $obj, $fk_col, $fk_id );
623     }
624     elsif ($ref) {
625         $LOG->warn("Can't insert object of type $ref");
626     }
627     else {
628         $LOG->warn("Attempted to insert scalar value into the database");
629     }
630     return undef;
631 }
632
633 sub _insert_array {
634     my ( $obj, $fk_col, $fk_id ) = @_;
635     my @entries;
636     foreach (@$obj) {
637         my $id = _insert( $_, $fk_col, $fk_id );
638         ref($id) eq 'ARRAY' ? push( @entries, @$id ) : push( @entries, $id );
639     }    #}
640     return \@entries;
641 }
642
643 sub _insert_obj {
644     my ( $obj, $fk_col, $fk_id ) = @_;
645     my ( $class, $table ) = ( ref($obj), _get_table($obj) );
646     my ( $id, %insert, %index, %children );
647
648     # Process object attributes
649     while ( my ( $col, $data ) = each %$obj ) {
650         if ( !$_types{$class}{$col} ) {
651             $LOG->warn("$col=>$data is not a valid field in $class");
652             delete $obj->{$col};
653         }
654         # Store Arrays of child objects to insert later
655         elsif ( $_types{$class}{$col} =~ m/(array)/ ) {
656             $children{$col} = $data;
657         }
658         # Insert child w/ 1 to 1 relationships and create a foreign key to it
659         elsif ( $_types{$class}{$col} =~ m/ref:(.*)/ ) {
660             if ( my $ft = $1->_get_table() ) {
661                 $insert{ $ft . '_fk' } = _insert($data);
662             }
663         }
664         # Add scalar attribute insert hash to be used @ INSERT time
665         else {
666             $insert{$col} = $dbhandle->quote($data);
667         }
668     }
669
670     # In case this is a child object, add the foreign key to parent
671     $insert{$fk_col} = $fk_id if ( $fk_col && $fk_id );
672
673     # Generate and execute SQL INSERT statement
674     my $sql =
675         "INSERT INTO $table ("
676       . join( ',', keys %insert )
677       . ") VALUES ("
678       . join( ',', values(%insert) ) . ')';
679     eval {
680         $LOG->debug($sql);
681         $dbhandle->do($sql);
682     };
683
684     # Handle DB errors. If 1062 (collision) get the ID of pre-existing row
685     if ($@) {
686         if ( $dbhandle->err == 1062 ) {
687             my $filter;
688             while ( my ( $col, $key_type ) = each %{ $_keys{$class} } ) {
689                 if ( $key_type == $KEY_UNIQUE || $key_type == $KEY_UNIQUE_MULT )
690                 {
691                     $filter->{$col} = $obj->{$col};
692                 }
693             }
694             my @rows = $class->_select( $filter, 'id' );
695             if (scalar @rows) {
696                 $id = $rows[0]->{id};
697             }
698             else {
699                 $LOG->fatal("Error: Can't resolve duplicate records\t" . $dbhandle->err . ": $@");
700                 Carp::croak("Error: Can't resolve duplicate records\n\t" . $dbhandle->err . ": $@");
701             }
702         }
703         else {
704             $LOG->fatal("Error: " . $dbhandle->err . ": $@");
705             Carp::croak("Error: " . $dbhandle->err . ": $@");
706         }
707     }
708     else {
709         $id = $dbhandle->{'mysql_insertid'};
710     }
711
712     # Insert Children
713     foreach ( keys %children ) {
714         my $rv = _insert( $children{$_}, $table . '_fk', $id );
715
716         #TODO: Handle Insert Failure
717     }
718     return $id;
719 }
720
721 =item select
722
723 Creates and executes a SQL SELECT statement and returns an array of hash refs
724 containing result rows. If no fields are specified, all fields are returned.
725 The first parameter is a hash reference to a query filter. The filter may be
726 followed by a list of field names to retrieve.
727
728   @my_objects = HoneyClient::DB::SomeObj->select($my_filter,@columns);
729
730 or
731
732   $my_objects_ref = HoneyClient::DB::SomeObj->select($my_filter,@columns);
733
734 B<Input>
735
736 The first parameter is a hash_ref containing a filter. The filter is used to
737 generate a SQL query.
738
739 The filter is followed by a list of column to select.
740
741 Both parameters are optional. If the first parameter is a scalar, it is assumed
742 that there is no filter.
743
744 B<**NOTE**> Currently it is not possible to include a child object (ref or
745 array type) in the filter. Only 'id's of child objects are accepted.
746
747 B<Return Value>
748
749 Returns the 'id' of the (parent) object inserted.
750
751 =cut
752
753 sub select {
754     my @results;
755     eval {
756         $dbhandle = HoneyClient::DB::_connect(%config);
757         @results  = _select(@_);
758         $dbhandle->disconnect() if $dbhandle;
759     };
760     if ($@) {
761         $LOG->fatal("select error: $@");
762         Carp::croak("select error: $@");
763         @results = ();
764     }
765     wantarray ? return @results : return \@results;
766 }
767
768 sub _select {
769     my ( $class, $filter, @fields ) = @_;
770
771     # If 2nd argument is not a hashref, assume it is the first field.
772     if ( $filter && ref($filter) ne 'HASH' ) {
773         unshift( @fields, $filter );
774         $filter = {};
775     }
776
777     # Prepare SQL statements
778     my $sql = "SELECT ";
779     $sql .=
780       (
781         scalar(@fields)
782         ? join( ',', @fields )
783         : join( ',', $class->get_fields() ) );
784     $sql .= " FROM " . $class->_get_table() . " WHERE ";
785     my @conditions;
786
787     # Set condition statements
788     while ( my ( $col, $data ) = each %$filter ) {
789         if ( !exists $_types{$class}{$col} ) {
790
791             # TODO: Handle non-existent field
792         }
793         elsif ( $_types{$class}{$col} =~ /array:.*/ ) {
794             @$data = map $dbhandle->quote($_), @$data;
795             push( @conditions, 'id IN (' . join( ',', @$data ) . ')' )
796               if ( scalar(@$data) );
797         }
798         elsif ( $_types{$class}{$col} =~ /ref:(.*)/ ) {
799             push @conditions,
800               ( $1->_get_table() . '_fk=' . $dbhandle->quote($data) );
801         }
802         else {
803             push @conditions, ( $col . '=' . $dbhandle->quote($data) );
804         }
805     }
806     $sql .= join( ' AND ', @conditions );
807
808     $LOG->debug($sql);
809     my @results = ();
810     my $sth = $dbhandle->prepare($sql);
811     $sth->execute();
812     while ( my $row = $sth->fetchrow_hashref() ) {
813         push @results, $row;
814     }
815    
816     return @results;
817 }
818
819 sub includes {
820     my @ids;
821     foreach (@_) {
822         push( @ids, $_ ) if ( !( ref $_ ) && ( $_ =~ /^\d+$/ ) );
823         if ( exists $_->{id} ) {
824             push @ids, $_->{id};
825         }
826         else {
827             next;    #push @ids, $_->_get_id();
828         }
829     }
830     return \@ids;
831 }
832
833 sub _get_table {
834     my $class = shift;
835     my ( $table, $ref );
836     ( $ref = ref($class) ) ? ( $table = $ref ) : ( $table = $class );
837     $table =~ s/HoneyClient::DB:://g;
838     $table =~ s/::/_/g;
839     $table;
840 }
841
842 =back
843
844 =head2 Utility Functions
845
846 =over 4
847
848 =item get_fields
849
850 Retrieves a list of fields as defined by the schema, excluding array fields. Can
851 be used in conjunction with calls to HoneyClient::DB::select to execute a SELECT
852 query that retrieves all fields.
853
854 =back
855
856 =cut
857
858 sub get_fields {
859     my $self = shift;
860     my $class = ( ref($self) or $self );
861
862     my @fields;
863     # Begin Fields list w/ record id
864     push @fields,'id';
865
866     foreach ( keys %{ $_types{$class} } ) {
867         if ( $_types{$class}{$_} !~ m/array:.*/ ) {
868             if ( $_types{$class}{$_} =~ m/ref:(.*)/ ) {
869                 push( @fields, $1->_get_table . '_fk' );
870             }
871             else { push @fields, $_; }
872         }
873     }
874     return @fields;
875 }
876
877 sub _connect {
878     my %conf = @_;
879     my $dsn  = "DBI:"
880       . $conf{driver}
881       . ":database="
882       . $conf{dbname}
883       . ";host="
884       . $conf{host}
885       . ";port="
886       . $conf{port};
887     my $dbh =
888       DBI->connect_cached( $dsn, $conf{user}, $conf{pass},
889         { 'RaiseError' => 1, 'PrintError' => 0 } );
890
891     if ( $dbh ne '' ) {
892         $dbh->{'AutoCommit'} = 0;    # In order to use Auto_Reconnect
893                                      #$dbh->{mysql_auto_reconnect} = 1;
894
895         #        _SigSetup(); # Signal handling if necessary
896         return $dbh;
897     }
898     else {
899         $LOG->fatal("__PACKAGE__->_Connect Failed: $DBI::errstr");
900         Carp::croak "__PACKAGE__->_Connect Failed: $DBI::errstr";
901     }
902 }
903
904 # Creates the table for the referenced class unless it exists
905
906 sub deploy_table {
907     my $class = shift;
908     my $table = $class->_get_table();
909
910     # Check for existence of table in DB
911     if ( table_exists($table) ) {
912         if ($debug) {
913             $LOG->warn("${class}->deploy_table: Table $table exists!!");
914         }
915         return 1;
916     }
917     $dbhandle = HoneyClient::DB::_connect(%config);
918     my ( @mult_unique_key, @foreign_keys, %arrays );
919
920     # Create SQL statement to create table
921     my $sql = "CREATE TABLE $table (\n"
922       . "\tid INT UNSIGNED AUTO_INCREMENT PRIMARY KEY";
923
924     # Process each column in the %_types table
925     while ( my ( $col, $type ) = each %{ $_types{$class} } )
926     {    #each %{$class."::fields"}) {
927             # Create a foreign key for reference types in new table
928         if ( $type =~ m/ref:(.*)/ ) {
929             $sql .= ",\n\t" . $1->_get_table() . "_fk INT UNSIGNED";
930             push @foreign_keys, $1;
931         }
932
933         # Create a foreign key to new table for array types in the child table
934         elsif ( $type =~ m/array:(.*)/ ) {
935             $arrays{$col} = $1;
936             next;
937         }
938
939         # Add column in new table for scalar data types
940         else {
941             $sql .= ",\n\t$col " . _get_db_type($type);
942         }
943
944         # Required columns will be added as NOT NULL
945         if ( exists $_required{$class} && $_required{$class}{$col} ) {
946             $sql .= " NOT NULL";
947         }
948
949         # Initial Values for columns
950