#----------------------------------------------------------------------
package DBIx::DataModel::Statement::Oracle;
#----------------------------------------------------------------------
use strict;
use warnings;
no strict 'refs';

use parent      qw/DBIx::DataModel::Statement/;
use mro         qw/c3/;
use DBD::Oracle 1.62 qw/:ora_fetch_orient :ora_exe_modes/;
use Carp::Clan qw[^(DBIx::DataModel::|SQL::Abstract)];


sub sqlize {
  my ($self, @args) = @_;

  # merge new args into $self->{args}
  $self->refine(@args) if @args;

  # reorganize pagination (code copied from SQL::Abstract::More), in order
  # to capture all cases of limit/offset
  my $page_index = delete $self->{args}{-page_index};
  my $page_size  = delete $self->{args}{-page_size};
  if ($page_index || $page_size) {
    not exists $self->{args}{$_} or croak "-page_size conflicts with $_"
      for qw/-limit -offset/;
    $self->{args}{-limit} = $page_size;
    if ($page_index) {
      $self->{args}{-offset} = ($page_index - 1) * $page_size;
    }
  }

  # remove -limit and -offset from args; they will be handled later in
  # prepare() and execute(), see below
  $self->{_ora_limit}  = delete $self->{args}{-limit};
  $self->{_ora_offset} = delete $self->{args}{-offset};

  # if there is an offset, we must ask for a scrollable statement
  $self->refine(-prepare_attrs => 
                  {ora_exe_mode => OCI_STMT_SCROLLABLE_READONLY})
     if $self->{_ora_offset};

  return $self->next::method();
}


# recompute page size/index from _ora_* fields
sub page_size   { my $self = shift;
                  $self->{_ora_limit}  || POSIX::LONG_MAX            }
sub page_index  { my $self = shift;
                  my $offset = $self->{_ora_offset} || 0;
                  return int($offset / $self->page_size) + 1 }


sub execute {
  my ($self, @args) = @_;
  $self->next::method(@args);

  # go to initial offset, if needed
  if (my $offset = $self->{_ora_offset}) {
    $self->{sth}->ora_fetch_scroll(OCI_FETCH_ABSOLUTE, $offset);
  }
  return $self;
}

sub next {
  my ($self, $n_rows) = @_;

  # first execute the statement
  $self->execute if $self->{status} < DBIx::DataModel::Statement::EXECUTED;

  # if needed, ajust $n_rows according to requested limit
  $n_rows = $self->{_ora_limit} if defined $self->{_ora_limit};

  # now regular handling can do the rest of the job;
  return $self->next::method($n_rows);
}

1;

__END__

=head1 NAME

DBIx::DataModel::Statement::Oracle - Statement for interacting with DBD::Oracle in Oracle versions prior to 12c

=head1 SYNOPSIS

  DBIx::DataModel->Schema("MySchema",
     statement_class => "DBIx::DataModel::Statement::Oracle",
  );

  my $rows = $source->select(
    ...,
    -limit  => 50,
    -offset => 200,
  );

=head1 DESCRIPTION

This subclass redefines some parent methods 
from L<DBIx::DataModel::Statement> in order to take advantage
of L<DBD::Oracle/"Scrollable Cursor Methods">.

This was interesting in ancient Oracle databases,
for applications that needed to do pagination within result sets.
In versions prior to 12c, Oracle had no support for LIMIT/OFFSET in SQL,
so this subclass uses some special methods of the Oracle driver to jump
to a specific row within a resultset, and then extract a limited
number of rows. The API is exactly the same as other, regular DBIx::DataModel implementations.

This mechanism will also work in Oracle versions 12c or superior, but it is no longer needed
in recent Oracle databases; instead, activate the C<Oracle12c> dialect in L<SQL::Abstract::More>,
like this :

  DBIx::DataModel->Schema("MySchema",
     sql_abstract_args => [sql_dialect => "Oracle12c"],
  );




=head1 AUTHOR

Laurent Dami, E<lt>laurent.dami AT etat  ge  chE<gt>, April 2012.

=head1 COPYRIGHT AND LICENSE

Copyright 2011 by Laurent Dami.

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.
