package PROP::Query::Object;

use strict;
use DBI;
use PROP::ResultSet::Object;
use PROP::ResultSet::Link;
use PROP::Query::Link;
use PROP::Constants;
use Hash::Util qw/lock_keys/;
use Carp;
use UNIVERSAL qw/isa/;

sub new {
    my ($invocant, $class, $local_conditions, $foreign_conditions,
	$orderings, $link_queries, $buffer_size, $limit) = @_;
    my $self = bless({}, ref($invocant) || $invocant);
    
    my $err_msg;

    unless(not($limit) or
	   (ref($limit) eq 'ARRAY' and scalar(@$limit) == 2 and
	    $limit->[0] =~ /\d+/ and $limit->[1] =~ /\d+/) or
	   (not(ref($limit)) and $limit =~ /\d+/))
    {
	$err_msg = "was expecting either an interger, " . 
	    "or an array ref holding two integers as seventh argument";
    }

    $err_msg = "was expecting an integer value as sixth argument"
	unless $buffer_size and $buffer_size =~ /\d+/;

    unless(ref($link_queries) eq 'ARRAY' and
	   (scalar(@$link_queries) == 0 or
	    not grep { not isa($_, 'PROP::Query::Link') } @$link_queries))
    {
	$err_msg = "was expecting an array reference of " . 
	    "PROP::Query::Link objects as fifth argument";
    }

    unless(ref($orderings) eq 'ARRAY' and
	   (scalar(@$orderings) == 0 or
	    not grep { not $class->has_field($_) } @$orderings))
    {
	$err_msg = "was expecting an array ref of orderings as fourth argument";
    }

    unless(ref($foreign_conditions) eq 'ARRAY' and
	   (scalar(@$foreign_conditions) == 0 or
	    not grep { not isa($_, 'PROP::Conditions::Foreign') } @$foreign_conditions))
    {
	$err_msg = "was expecting an array ref of " . 
	    "PROP::Conditions::Foreign objects as third argument";
    }

    $err_msg = "was expecting a PROP::Conditions::Local object as second argument"
	unless isa($local_conditions, 'PROP::Conditions::Local');

    $err_msg = "was expecting a class name of a subclass of PROP::Object as first argument"
	unless isa($class, 'PROP::Object');

    if($err_msg) {
	my ($pkg, $file, $line) = caller();
	die new PROP::Exception::IllegalArgument($err_msg, $file, $line);
    }

    $self->{-class} = $class;
    $self->{-local_conditions} = $local_conditions;
    $self->{-foreign_conditions} = $foreign_conditions;
    $self->{-orderings} = $orderings;
    $self->{-link_queries} = $link_queries;
    $self->{-buffer_size} = $buffer_size;
    $self->{-limit} = $limit;

    lock_keys(%$self) if DEBUG;
    return $self;
}

sub get_class {
    my ($self) = @_;
    return $self->{-class};
}

sub get_foreign_conditions {
    my ($self) = @_;
    return $self->{-foreign_conditions};
}

sub get_local_conditions {
    my ($self) = @_;
    return $self->{-local_conditions};
}

sub get_link_queries {
    my ($self) = @_;
    return $self->{-link_queries};
}

sub push_conditional_expression {
    my ($self, $condition) = @_;
    $self->{-local_conditions}->push_expression($condition);
}

sub get_bindings {
    my ($self) = @_;
    return $self->{-local_conditions}->get_bindings();
}

sub push_binding {
    my ($self, $binding) = @_;
    $self->{-local_conditions}->push_binding($binding);
}

sub get_orderings {
    my ($self) = @_;
    return $self->{-orderings};
}

sub get_buffer_size {
    my ($self) = @_;
    return $self->{-buffer_size};
}

sub get_limit {
    my ($self) = @_;
    return $self->{-limit};
}

sub execute {
    my ($self) = @_;

    return new PROP::ResultSet::Object($self, $self->{-link_queries});
}

1;

=head1 Name

PROP::Query::Object

=head1 Description

Objects of this class are used to specify how queries on collections
of objects are performed.

=head1 Synopsis

 $qo = PROP::Query::Object->new($class,
			        $lc,
			        [$fc1, $fc2, ..., $fcn],
			        ['field1', 'field2'],
			        [$lq1, $lq2, ..., $lqn],
			        $buffer_size,
				$limit);

 $rso = new PROP::ResultSet::Object($qo);

=head1 Methods

=over

=item new

 $oq = PROP::Query::Object->new($class,
			        $local_conditions,
			        $foreign_conditions,
			        $orderings,
			        $link_queries,
			        $buffer_size,
				$limit);

This method constructs a query as follows...

=over

=item $class

This argument specifies the class of the collection to be queried,
which must be a subclass of PROP::Object.

=item $local_conditions

This argument is an instance of PROP::Conditions::Local.  It is used to
specify which objects of the class will be loaded as per restrictions
on its own field values.

=item $foreign_conditions

This argument is an array reference containing zero or more instances
of the class PROP::Conditions::Foreign.  The PPO::Conditions::Foreign
objects specify which objects of the class will be loaded as per
restrictions on the objects with which it is associated, e.g. "only
load objects that have children that have such and such values".

=item $orderings

This argument is an array reference that contains zero or more fields
by which to order the results of the query.

=item $link_queries

This argument is an array reference that contains zero or more
PROP::Query::Link objects that specify which related objects should be
loaded in the course of executing the query.

=item $buffer_size

This argument specifies the maximum number of rows that will be pulled
into memory at a time.  Programmatically speaking, this argument has
no ultimate effect on what the resulting result set will contain, but
can potentially affect performance dramatically, and as such it may be
desirable to tune it, taking into account the size of the things being
queried and the memory/cpu/disk of the machine.  Basically, you want
to load as many rows at a time as you can without choking the system's
resources.

=item $limit

This argument specifies a maximum number of rows to return, and
possibly also an offset into the result being returned.  It has two
forms: it can be an integer value, in which case it is interpreted as
the maximum number of rows to return; it can be an array reference
that holds two intergers, the first being the offset into the results
at which to start returning results, and the second being the maximum
number of results to return.

=back

=item get_class

 $oq->get_class()

This method returns the class of the object collection that is being queried.

=item get_local_conditions

 $oq->get_local_conditions()

This method returns the PROP::Conditions::Local object that is
associated with this query, i.e. the one that was passed into its
constructor.

=item get_foreign_conditions

 $oq->get_foreign_conditions()

This method returns the array reference of PROP::Conditions::Foreign
objects that are associated with this query, i.e. the ones that were
passed into its constructor.

=item get_orderings

 $oq->get_orderings()

This method returns the reference to the array that holds the list of
fields by which the results of the query should be ordered.

=item get_buffer_size

 $oq->get_buffer_size()

This method returns the integer value that is the maximum number of
fields that will be loaded from the underlying database at a time.

=back

=head1 Author

Andrew Gibbs (awgibbs@awgibbs.com,andrew.gibbs@nist.gov)

=head1 Legalese

This software was developed at the National Institute of Standards and
Technology by employees of the Federal Government in the course of
their official duties. Pursuant to title 17 Section 105 of the United
States Code this software is not subject to copyright protection and
is in the public domain. PROP is an experimental system. NIST
assumes no responsibility whatsoever for its use by other parties, and
makes no guarantees, expressed or implied, about its quality,
reliability, or any other characteristic. We would appreciate
acknowledgement if the software is used.  This software can be
redistributed and/or modified freely provided that any derivative
works bear some notice that they are derived from it, and any modified
versions bear some notice that they have been modified.
