The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Class::AutoDB::Cursor;
use vars qw(@ISA @AUTO_ATTRIBUTES @OTHER_ATTRIBUTES %SYNONYMS %DEFAULTS);
use strict;
use DBI;
use Class::AutoClass;
# use Hash::AutoHash::Args;
@ISA = qw(Class::AutoClass);

# Cursor for AutoDB queries

@AUTO_ATTRIBUTES=qw(query dbh select_sth count_sth is_started object_table);
@OTHER_ATTRIBUTES=qw();
%SYNONYMS=();
%DEFAULTS=(object_table=>'_AutoDB',is_started=>0);
Class::AutoClass::declare(__PACKAGE__);

# NG 09-03-19: commented out _init_self -- stub not needed
# sub _init_self {
#   my($self,$class,$args)=@_;
#   return unless $class eq __PACKAGE__; # to prevent subclasses from re-running this
# }
sub get {
  my($self,$n)=@_;
  $self->reset unless $self->is_started;
  my($dbh,$sth)=($self->dbh,$self->select_sth);
  my $rows=$sth->fetchall_arrayref(undef,$n) || 
    $self->throw("Database error in fetch for query\n".$self->select_sql."\n".$dbh->errstr);
  my @objects;
  for my $row (@$rows) {
    my($oid,$freeze)=@$row;
    my $object=Class::AutoDB::Serialize::thaw($oid,$freeze);
    # NG 10-09-13: skip deleted objects
    next if 'Class::AutoDB::OidDeleted' eq ref $object;
    push(@objects,$object);
  }
  wantarray? @objects: \@objects;
}
#*get_all=\&get;

sub get_next {
  my($self)=@_;
  $self->reset unless $self->is_started;
  my($dbh,$sth)=($self->dbh,$self->select_sth);
  return undef unless $sth && $sth->{Active};
  # NG 10-09-13: added outer loop to skip deleted objects
  while(1) {
    my($oid,$freeze)=$sth->fetchrow_array;
    # NG 10-09-13: changed test to support deleted objects ($freeze undef)
    # if (!$freeze) {		# either end of results or error
    if (!defined $oid) {		# either end of results or error
      return undef unless $dbh->err;	# end of results
      $self->throw("Database error in fetch for query\n".$self->select_sql."\n".$dbh->errstr);
    } else {
      my $object=Class::AutoDB::Serialize::thaw($oid,$freeze);
      # NG 10-09-13: skip deleted objects
      return $object unless 'Class::AutoDB::OidDeleted' eq ref $object;
    }
  }
}
sub count {
  my($self)=@_;
  my($sql,$dbh,$sth)=($self->count_sql,$self->dbh,$self->count_sth);
  if ($sql && $dbh && !$sth) {
    $sth=$dbh->prepare($sql) || 
      $self->throw("Database error in prepare for query\n$sql\n".$dbh->errstr);
    $sth->execute || $self->throw("Database error in exectue for query\n$sql\n".$dbh->errstr);
    $self->count_sth($sth);
  } elsif ($sql && $sth) {
    $sth->execute || $self->throw("Database error in exectue for query\n$sql\n".$dbh->errstr);
  }
  # NG 09-12-14: was throwing error when count==0! scary this wasn't caught sooner
  #  my($count)=$sth->fetchrow_array || 
  #    $self->throw("Database error in fetch for query\n$sql\n".$dbh->errstr);
  my($count)=$sth->fetchrow_array || 
    ($dbh->err && $self->throw("Database error in fetch for query\n$sql\n".$dbh->errstr));
  $count || 0;
}
sub select_sql {
  my($self)=@_;
  my($object_table,$query)=($self->object_table,$self->query);
  my $sql="SELECT $object_table.oid,$object_table.object $query";
  $sql;
}
sub count_sql {
  my($self)=@_;
  my($object_table,$query)=($self->object_table,$self->query);
  my $sql="SELECT COUNT($object_table.oid) $query";
  $sql;
}
sub reset {
  my($self)=@_;
  my($sql,$dbh,$sth)=($self->select_sql,$self->dbh,$self->select_sth);
  if ($sql && $dbh && !$sth) {
    $sth=$dbh->prepare($sql) || 
      $self->throw("Database error in prepare for query\n$sql\n".$dbh->errstr);
    $sth->execute || $self->throw("Database error in exectue for query\n$sql\n".$dbh->errstr);
    $self->select_sth($sth);
  } elsif ($sql && $sth) {
    $sth->execute || $self->throw("Database error in exectue for query\n$sql\n".$dbh->errstr);
  }
  $self->is_started(1);
}

# TODO: decide on has_more -- requires a look ahead buffer -- not a big deal, but do we need it?

# TODO: implement find - conjoins existing SQL with new query
sub find {
}
sub do_sql {
  my $self=shift;
  my @sql=_flatten(@_);
  $self->throw("Cannot run SQL unless connected") unless $self->is_connected;
  my $dbh=$self->dbh;
  for my $sql (@sql) {
    next unless $sql;
    $dbh->do($sql);
    $self->throw("SQL error: ".$dbh->errstr) if $dbh->err;
  }
}
sub _flatten {map {'ARRAY' eq ref $_? @$_: $_} @_;}

1;