root/honeyclient/branches/rel/1.0/lib/HoneyClient/DB.pm

Revision 994, 42.4 kB (checked in by kindlund, 1 year ago)

Bumped version to v1.00.

  • 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 1.00.
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 = 1.00;
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 our %display_rank;
371
372 # %fields must be defined by all children classes
373 our %fields;
374
375 our $last_error_code;
376
377 #constants
378 # Integrity status field
379 our ( $STATUS_DELETED, $STATUS_ADDED, $STATUS_MODIFIED ) = ( 0, 1, 2 );
380 # Uniqueness of Attributes
381 our ( $KEY_INDEX, $KEY_UNIQUE, $KEY_UNIQUE_MULT ) = ( 0, 1, 2 );
382 # Option for get_fields()
383 our ( $FIELDS_ALL, $FIELDS_SEARCH, $FIELDS_DISPLAY ) = ( 0 , 1, 2);
384 # Error Codes
385 our ($ERROR_NONE,$ERROR_INSERT_FAILED,$ERROR_DUPLICATE_FOUND,$ERROR_DUPLICATE_UNRESOLVED)
386     = (0,1,2,3);
387 our @ERROR_MESSAGES = (
388     "Success",
389     "Failed with a fatal error",
390     "Duplicate object found. Non-fatal warning",
391     "Duplicate object found. Unable to retrieve ID of duplicate record",
392 );
393 our $LAST_ERROR = $ERROR_NONE;
394
395 # Initialize Connection
396 our %config;
397 $config{driver} = "mysql";
398 $config{host}   = getVar( name => "host", namespace => "HoneyClient::DB" );
399 $config{port}   = getVar( name => "port", namespace => "HoneyClient::DB" );
400 $config{user}   = getVar( name => "user", namespace => "HoneyClient::DB" );
401 $config{pass}   = getVar( name => "pass", namespace => "HoneyClient::DB" );
402 $config{dbname} = getVar( name => "dbname", namespace => "HoneyClient::DB" );
403
404 if ( !db_exists(%config) ) {
405     die;
406 }
407
408 END {
409     $dbhandle->disconnect() if $dbhandle;
410 }
411
412 =pod
413
414 =head1 METHODS
415
416 =head2 Object Creation
417
418 =over 4
419
420 =item new
421
422 Receives an unblessed hash, imports the schema (if necessary), checks that
423 required fields contain the proper data, and returns the blessed object.
424
425 It must be called using an object class derived from HoneyClient::DB.
426 For Example:
427
428   $my_obj = HoneyClient::DB::SomeObj->new({
429           field_a => "foo",
430           field_b => "bar"
431   });
432
433 =cut
434
435 sub new {
436     my ( $class, $self ) = @_;
437     bless( $self, $class );
438
439     # Check if Schema has been imported
440     import_schema($class) if ( !exists( $_types{$class} ) );
441
442     # Make sure required Attributes are set. Fail if not.
443     my @missing = $self->_check_required();
444     if ( scalar @missing ) {
445         $LOG->fatal( "$class->new(): Object missing required attribute(s): " .
446             join( ', ', @missing ) . '.' );
447         Carp::croak( "$class->new(): Object missing required attribute(s): " .
448             join( ', ', @missing ) . '.\n' );
449     }
450
451     # Check data validity. Initialize uninitialized ref and array objects
452     foreach my $key ( keys %$self ) {
453         eval {
454             if ( $self->{$key} )
455             {
456                 $self->{$key} = $_check{$class}{$key}->( $self->{$key} );
457             }
458         };
459         if ($@) {
460             $LOG->fatal("Invalid Object $key\t$@");
461             Carp::croak "Invalid Object $key\n\t$@";
462         }
463         if ( $_types{$class}{$key} =~ m/(array|ref):(.*)/ ) {
464             my $ref = ref( $self->{$key} );
465             my ($childType,$childClass)  = ($1,$2);
466
467             if ( $childClass->can('new') ) {
468                 if ( $childType eq 'ref' ) {
469                     $self->{$key} = $childClass->new( $self->{$key} );
470                 }
471                 if ( $ref eq 'ARRAY' and $childType eq 'array' ) {
472                     foreach my $obj ( @{ $self->{$key} } ) {
473                         $obj = $childClass->new($obj);
474                     }
475                 }
476             }
477             else {
478                 $LOG->fatal("Invalid Object! $childType does not exist");
479                 Carp::croak "Invalid Object! $childType does not exist";
480             }
481         }
482     }
483     return $self;
484 }
485
486 ################# Initialization Helper Functions #################
487
488 sub _check_required {
489     my $self  = shift;
490     my $class = ref $self;
491
492     # make sure field is not undefined if 'required' option is set
493     if ( exists $_required{$class} ) {
494         my @missing;
495         foreach ( keys %{ $_required{$class} } ) {
496             push( @missing, $_ )
497               if ( !defined( $self->{$_} ) or ( $self->{$_} eq "" ) );
498         }
499         return @missing;
500     }
501     return;
502 }
503
504 sub import_schema {
505     my $class  = shift;
506     my $schema = \%{ $class . "::fields" };
507     my (%rank_display, %rank_search);
508     my $MAX_RANK = 10000;
509
510     # Parase Attributes; store types and options.
511     while ( my ( $type, $attrib ) = each(%$schema) ) {
512         my $ref = ref $attrib;
513
514         # Attributes in array format use default options
515         if ( $ref eq 'ARRAY' ) {
516             foreach ( @{$attrib} ) {
517                 $_types{$class}{$_} = $type;
518                 if ( $type =~ m/(ref|array)/ ) {
519                     delete $_types{$class};
520                     $LOG->fatal("Invalid Object Type. ref AND array types must "
521                       . "be defined as a hash containing 'objclass'");
522                     Carp::croak "Invalid Object Type. ref AND array types must "
523                       . "be defined as a hash containing 'objclass'";
524                 }
525                 $_check{$class}{$_} = $defaults{$type}{check_func}
526                   or $_check{$class}{$_} = \&check_nothing;
527             }
528         }
529
530         # Parse options for attributes in hash table format
531         elsif ( $ref eq 'HASH' ) {
532             while ( my ( $a, $opts ) = each %$attrib ) {
533                 $_types{$class}{$a} = $type;
534                 if ( exists $opts->{required} && $opts->{required}) {
535                     $_required{$class}{$a} = 1;
536                 }
537
538                 # array and ref types require the objclass option
539                 if ( $type =~ m/^(array|ref)$/ ) {
540                     if ( !exists $opts->{objclass} ) {
541                         $LOG->fatal("$1 of unknown class: $a");
542                         Carp::croak "$1 of unknown class: $a";
543                     }
544                     if ( !exists $_types{ $opts->{objclass} } ) {
545                         import_schema( $opts->{objclass} );
546                     }
547                     $_types{$class}{$a} .= ':' . $opts->{objclass};
548                 }
549
550                 # Check function will ensure data is of proper format
551                 if ( $opts->{check_func} ) {
552                     $_check{$class}{$a} = $opts->{check_func};
553                 }
554                 else {
555                     $_check{$class}{$a} = $defaults{$type}{check_func}
556                       or $_check{$class}{$a} = \&check_nothing;
557                 }
558
559                 # key option determines if attribute is an index
560                 if ( $opts->{key} ) {
561                     $_keys{$class}{$a} = $opts->{key};
562                 }
563                 if ( $opts->{init_val} ) {
564                     $_init_val{$class}{$a} = $opts->{key};
565                 }
566                 #DEBUG: Test new code
567                 if( $opts->{searchable} ) {
568                     #TODO: $_search_fields{$class}
569                 }
570                 if( exists $opts->{display_rank} ) {
571                     my $rank = $opts->{display_rank};
572                     if ($opts->{display_rank}) {
573                         while (exists $rank_display{$rank}) {
574                                 $rank++;
575                         }
576                         $rank_display{$rank} = $a;
577                     }
578                 }
579                 elsif ($type =~ m/^(array|ref)$/) {}
580                 else {
581                     $rank_display{$MAX_RANK++} = $a;
582                 }
583             }
584         }
585         else {
586             $LOG->warn("$class\{$type\} is defined improperly");
587         }
588     }
589     my @temp = map $rank_display{$_}, sort( keys( %rank_display ) );
590     $display_rank{$class} = \@temp;
591
592     # Add the table to the DB if necessary
593     if (!$class->deploy_table()) {
594         $LOG->fatal("${class}->import_schema: " . "Failed to deploy table");
595         Carp::croak("${class}->import_schema: " . "Failed to deploy table");
596     }
597 }
598
599 =back
600
601 =head2 Database Operations
602
603 =over 4
604
605 =item insert
606
607 Creates and executes a SQL INSERT statement for the referenced object. The
608 object must be initialized at the time this method is called.
609
610   $my_obj->insert();
611
612 B<Input>
613
614 There are no parameters, however the calling object is used as input for the
615 insert operation.
616
617 B<Return Value>
618
619 Returns the 'id' of the (parent) object inserted.
620
621 =cut
622
623 sub insert {
624     my $obj = shift;
625     my $id  = undef;
626     my $objType  = ref $obj;
627
628     $dbhandle = HoneyClient::DB::_connect(%config);
629
630     # Attempt insert; commit if succeeds, else rollback
631     $LOG->debug("Attempting insert operation.");
632     eval { $id = _insert( $obj, undef ); };
633     if ($@) {
634         $LOG->warn("insert failed, Rolling Back: $@");
635         $dbhandle->rollback();
636     }
637     elsif ($LAST_ERROR != $ERROR_NONE) {
638         $LOG->warn("Rolling Back $objType Insert. Code #$LAST_ERROR: ".$ERROR_MESSAGES[$LAST_ERROR]);
639         $dbhandle->rollback();
640     }
641     else {
642         $dbhandle->commit();
643     }
644     $dbhandle->disconnect() if $dbhandle;
645     return $id;
646 }
647
648 ##################### Insert Helper Functions #####################
649
650 sub _insert {
651     my ( $obj, $fk_col, $fk_id ) = @_;
652     my $ref = ref $obj;
653
654     if ( $ref eq 'ARRAY' ) {
655         return _insert_array( $obj, $fk_col, $fk_id );
656     }
657     elsif ( exists $_types{$ref} ) {
658         return _insert_obj( $obj, $fk_col, $fk_id );
659     }
660     elsif ($ref) {
661         $LOG->warn("Can't insert object of type $ref");
662     }
663     else {
664         $LOG->warn("Attempted to insert scalar value into the database");
665     }
666     return undef;
667 }
668
669 sub _insert_array {
670     my ( $obj, $fk_col, $fk_id ) = @_;
671     my @entries;
672     foreach (@$obj) {
673         my $id = _insert( $_, $fk_col, $fk_id );
674         ref($id) eq 'ARRAY' ? push( @entries, @$id ) : push( @entries, $id );
675     }    #}
676     return \@entries;
677 }
678
679 sub _insert_obj {
680     my ( $obj, $fk_col, $fk_id ) = @_;
681     my ( $class, $table ) = ( ref($obj), _get_table($obj) );
682     my ( $id, %insert, %index, %children );
683     my $error = $ERROR_NONE;
684
685 # Process object attributes
686     while ( my ( $col, $data ) = each %$obj ) {
687         if ( !$_types{$class}{$col} ) {
688             $LOG->warn("$col=>$data is not a valid field in $class");
689             delete $obj->{$col};
690         }
691 # Store Arrays of child objects to insert later
692         elsif ( $_types{$class}{$col} =~ m/(array)/ ) {
693             $children{$col} = $data;
694         }
695 # Insert child w/ 1 to 1 relationships and create a foreign key to it
696         elsif ( $_types{$class}{$col} =~ m/ref:(.*)/ ) {
697             if ( my $ft = $1->_get_table() ) {
698                 $insert{ $ft . '_' .$col . '_fk' } = _insert($data);
699             }
700         }
701 # Add scalar attribute insert hash to be used @ INSERT time
702         else {
703             $insert{$col} = $dbhandle->quote($data);
704         }
705     }
706 # In case this is a child object, add the foreign key to parent
707     $insert{$fk_col} = $fk_id if ( $fk_col && $fk_id );
708
709 # Generate and execute SQL INSERT statement
710     my $sql = "INSERT INTO $table (" . join( ',', keys %insert ) . ") VALUES (" . join( ',', values(%insert) ) . ')';
711     eval {
712         $LOG->debug($sql);
713         $dbhandle->do($sql);
714     };
715 # Handle DB errors. If 1062 (collision) get the ID of pre-existing row
716     if ($@) {
717         if ( $dbhandle->err == 1062 ) {
718             my $filter;
719             while ( my ( $col, $key_type ) = each %{ $_keys{$class} } ) {
720                 if ( $key_type == $KEY_UNIQUE || $key_type == $KEY_UNIQUE_MULT )
721                 {
722                     $filter->{$col} = $obj->{$col};
723                 }
724             }
725
726             my @rows = $class->_select(
727                 '-columns'=>['id'],
728                 '-where'=>$filter,
729             );
730             if (scalar @rows) {
731                 $id = $rows[0]->{id};
732                 $error = $ERROR_DUPLICATE_FOUND;
733             }
734             else {
735                 $LAST_ERROR = $ERROR_DUPLICATE_UNRESOLVED;
736                 $LOG->fatal("Error: Can't resolve duplicate records\t" . $dbhandle->err . ": $@");
737                 Carp::croak("Error: Can't resolve duplicate records\n\t" . $dbhandle->err . ": $@");
738             }
739         }
740         else {
741             $LAST_ERROR = $ERROR_INSERT_FAILED;
742             $LOG->fatal("Error #".$dbhandle->err."while executing SQL:\n\t$sql\n$@");
743             Carp::croak("Error #".$dbhandle->err."while executing SQL:\n\t$sql\n$@");
744         }
745     }
746     else {
747         $id = $dbhandle->{'mysql_insertid'};
748     }
749 # Insert Children
750     foreach ( keys %children ) {
751         my $rv = _insert( $children{$_}, $table . '_' .$_ . '_fk', $id );
752     }
753     $LAST_ERROR = $error;
754     return $id;
755 }
756
757 =head3 QUERY CONDITIONS
758
759 The following functions accept conditions using the '-where' argument. These
760 conditions filter results to the desired information. Currently any scalar field
761 is searchable (string, int, text, timestamp), while ref objects are searchable
762 only by their ids (for now).
763
764 The following query will list Regkey changes that occurred to the Windows
765 auto-start key .
766
767   @runKeys = HoneyClient::DB::Regkey->select(
768       -where => {
769           key_name => 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run',
770       },
771   );
772
773 In order to perform a partial match search, a tilda can be added to the front
774 of the column name. This is not applicable to ranges. The following query will
775 return any Registry change with the string 'Run' in the key name(path).
776
777   @runKeys = HoneyClient::DB::Regkey->select(
778       -where => {
779           '~key_name' => 'Run',
780       },
781   );
782
783 B<NOTE:> In this case, the column name must be quoted, or used as a variable.
784
785 A query can search a range, for instance, the following will show all
786 Honeyclients in a given date range.
787
788   @octoberClients = HoneyClient::DB::Client->select(
789       -where => {
790           start_time => ['2007-10-01 00::00::00','2007-10-31 23::59::59'
791       },
792   );
793
794 The '-where' option accepts filters on multiple columns as well.
795
796
797 =item select
798
799 Creates and executes a SQL SELECT statement and returns an array of hash refs
800 containing result rows.
801
802 The following would select the name, age, address, and phone columns for all
803 records with the name 'bob', and an age in the range of 25-50 (inclusive).
804
805   @my_objects = HoneyClient::DB::Process->select(
806       -columns => qw(name,pid,parent_name,created_time),
807       -where => {
808           name => 'bob',
809           age => [25,50],
810       },
811   );
812
813 B<Input>
814
815 The select() function can take as arguments a hash containing the following keys:
816
817 =item * B<'-columns'>
818
819 List of columns to be selected. By default, if no -columns argument is given,
820 all columns are selected. See L<get_fields>.
821
822 =item * B<'-where'>
823
824 This field can contain a set of conditions used to filter the results. Refer to
825 L<QUERY CONDITIONS> for more info.
826
827 =cut
828
829 sub select {
830 # Connect, Create, and execute SQL query
831     $dbhandle = HoneyClient::DB::_connect(%config);
832     my @results = _select(@_);
833
834 # Disconnect and return results
835     $dbhandle->disconnect() if $dbhandle;
836     wantarray ? return @results : return \@results;
837 }
838
839 sub _select {
840     my (@results,$sql);
841     eval {
842 # Create SQL query
843         $sql = _build_query(@_);
844         $LOG->debug($sql);
845
846 # Execute SQL query
847         my $sth = $dbhandle->prepare($sql);
848         $sth->execute();
849
850 # Retrieve results and exit
851         while ( my $row = $sth->fetchrow_hashref() ) {
852             push @results, $row;
853         }
854     };
855     if ($@) {
856         $LOG->warn("Error while executing SQL:\n\t$sql\n$@");
857         return ();
858     }
859     return @results;
860 }
861
862 sub _build_query {
863     my ( $class, %args) = @_;
864     my @fields;
865     if (exists($args{'-columns'}) && scalar(@{$args{'-columns'}})) {
866         @fields = @{$args{'-columns'}};
867     }
868     else {
869         @fields = $class->get_fields();
870     }
871 # Column Rename where columns are passed as a hash table e.g. {col_name => "Display Name"} becomes "col_name AS 'Display Name'"
872     foreach my $col_def (@fields) {
873         if (ref $col_def eq 'HASH') {
874              while (my ($col,$as) = (each %$col_def)) {
875                  push @fields, "$col AS '$as'";
876              }
877          }
878      }
879 # Prepare SQL statement
880     my $sql = "SELECT "; $sql .= join( ',', @fields); $sql .= " FROM " . $class->_get_table();
881 # Set condition statements
882     $sql .= $class->_where_condition($args{'-where'});
883     return $sql;
884 }
885
886 # Generates a where condition (e.g. " WHERE id=5 AND name='foo'")
887 sub _where_condition {
888     my ($class,$cond) = @_;
889     my @conditions;
890     if (!keys(%$cond)) {
891         return '';
892     }
893     while ( my ( $col, $data ) = each %$cond ) {
894         my $partial = 0;
895         if ($col =~ /^\~(.*)/) {
896             $col = $1;
897             $partial = 1;
898         }
899         if ( !exists $_types{$class}{$col} && $col ne 'id' && $col !~ m/_fk$/) {
900             next; #TODO: Handle non-existent field
901         }
902         my $cf = ($_check{$class}{$col} || \&check_nothing);
903         if (my $ref = ref($data)) {
904             eval {
905                 if (my ($type,$sub_class) = $_types{$class}{$col} =~ /(array|ref):(.*)/ ) {
906                     if ($ref eq 'HASH') {
907                         my ($left,$right) = ('id',$class->get_col_name($col));
908                         if ($type eq 'ref') {
909                             ($left, $right) = ($right,$left);
910                         }
911                         my $sub_query = $sub_class->select(
912                            -columns => [$right],
913                            -where => $data,
914                         );
915                         push @conditions, $left.' IN ('.$sub_query.')';
916                     }
917                 }
918                 elsif ($ref eq 'ARRAY' && &$cf($data->[0]) && &$cf($data->[1])) {
919                     push @conditions, ( $class->get_col_name($col) . ' BETWEEN ' .
920                         $dbhandle->quote($data->[0]) . ' AND '.
921                         $dbhandle->quote($data->[1])
922                     );
923                 }
924             };
925             #TODO: Handle Failure
926         }
927         else {
928             if (exists $_types{$class}{$col} && $_types{$class}{$col} =~ /ref:(.*)/ ) {
929                if (check_int($data)) {
930                    push @conditions,($class->get_col_name($col).